From 062f3ca2d7151c3675ce3cb510a2d5d3db33ea8b Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Fri, 19 Dec 2025 01:47:03 -0500 Subject: [PATCH 01/19] Fix issue where shop sign isn't updated on click, also, fix issue where shop keeper ui isn't opened when shop is frozen. --- .../shop/interaction/behaviors/ControlPanelUI.java | 8 +++----- .../quickshop/shop/interaction/behaviors/TradeUI.java | 5 +++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/ControlPanelUI.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/ControlPanelUI.java index d66dd8228c..68dc1d06b8 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/ControlPanelUI.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/ControlPanelUI.java @@ -25,6 +25,7 @@ import com.ghostchu.quickshop.api.shop.interaction.InteractionType; import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermissionGroup; import com.ghostchu.quickshop.menu.ShopKeeperMenu; +import com.ghostchu.quickshop.util.Util; import net.tnemc.menu.core.compatibility.MenuPlayer; import net.tnemc.menu.core.manager.MenuManager; import net.tnemc.menu.core.viewer.MenuViewer; @@ -85,11 +86,8 @@ public void handle(final @NotNull QuickShopAPI plugin, final @Nullable Shop shop return; } - if(shop.isFrozen()) { - ((QuickShop)plugin).text().of(event.getPlayer(), "shop-cannot-trade-when-freezing").send(); - return; - } - + Util.playClickSound(player); + shop.setSignText(((QuickShop)plugin).text().findRelativeLanguages(player)); MenuManager.instance().addViewer(viewer); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/TradeUI.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/TradeUI.java index cd7b7287d7..02f06b87a7 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/TradeUI.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/behaviors/TradeUI.java @@ -71,6 +71,8 @@ public void handle(final @NotNull QuickShopAPI plugin, final @Nullable Shop shop event.getBlockFace(), event.getHand(), event.getItem())) { + + //cancel our interaction. event.setCancelled(true); event.setUseInteractedBlock(Event.Result.DENY); event.setUseItemInHand(Event.Result.DENY); @@ -82,12 +84,15 @@ public void handle(final @NotNull QuickShopAPI plugin, final @Nullable Shop shop return; } + //open our menus. final MenuViewer viewer = new MenuViewer(event.getPlayer().getUniqueId()); viewer.addData(ShopKeeperMenu.SHOP_DATA_ID, shop.getShopId()); MenuManager.instance().addViewer(viewer); final MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(event.getPlayer()); MenuManager.instance().open("qs:trade", 1, menuPlayer); + + //cancel our item use event.setCancelled(true); event.setUseInteractedBlock(Event.Result.DENY); event.setUseItemInHand(Event.Result.DENY); From e09e1f8537b010a7723e33e783ad2c6a72a5c30a Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sat, 20 Dec 2025 14:46:43 -0500 Subject: [PATCH 02/19] Refactor database export and import processes: switch to `QuickShopCsvTransfer` for handling operations with CSV zip files, update table interaction logic, and adjust interaction.yml for new container configurations. --- .changelog/6.2.0.11.md | 5 +- .../command/subcommand/SubCommand_Export.java | 10 +- .../subcommand/SubCommand_Recovery.java | 7 +- .../quickshop/database/DataTables.java | 4 + .../database/QuickShopCsvTransfer.java | 277 ++++++++++++++++++ .../quickshop/shop/SimpleShopManager.java | 5 + .../com/ghostchu/quickshop/util/ShopUtil.java | 6 +- .../src/main/resources/interaction.yml | 6 +- 8 files changed, 310 insertions(+), 10 deletions(-) create mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java diff --git a/.changelog/6.2.0.11.md b/.changelog/6.2.0.11.md index a19b452621..13964551dc 100644 --- a/.changelog/6.2.0.11.md +++ b/.changelog/6.2.0.11.md @@ -97,6 +97,8 @@ - Bumped Residence version for the compat(thanks to YuanYuanOwO) - add world whitelist support(thanks to wling-art) - added /qs database save command, which saves all currently dirty-marked shops to the database. +- Split up interactions _SHOPBLOCK into _SHOPBLOCK and _CONTAINER. + - _SHOPBLOCK relates to shops, where _CONTAINER relates to shop-valid containers that are not currently shops. ## Fixes - Fixes issue with item price restrictions(thanks to maxcom1) @@ -114,4 +116,5 @@ - Fixed issue with 1.20.4 packetevents virtual displays not staying put. - Fixes critical issue in GriefPrevention compatibility plugin. - Fixes issue with shop owner not getting money when benefits active. -- Fixes for lands addon on folia, Use regionThread for Folia compatibility(thanks to YusakiDev) \ No newline at end of file +- Fixes for lands addon on folia, Use regionThread for Folia compatibility(thanks to YusakiDev) +- Fixes for TRADE_DIRECT on stacking shops \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java index 7456cd2b52..23a14a81c6 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java @@ -4,6 +4,7 @@ import com.ghostchu.quickshop.api.command.CommandHandler; import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; +import com.ghostchu.quickshop.database.QuickShopCsvTransfer; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; import com.ghostchu.quickshop.util.Util; import org.bukkit.command.ConsoleCommandSender; @@ -31,14 +32,15 @@ public synchronized void onCommand(@NotNull final ConsoleCommandSender sender, @ final DatabaseIOUtil databaseIOUtil = new DatabaseIOUtil((SimpleDatabaseHelperV2)plugin.getDatabaseHelper()); Util.asyncThreadRun(()->{ try { - databaseIOUtil.exportTables(file); + //databaseIOUtil.exportTables(file); + QuickShopCsvTransfer.exportTablesToZip(file); + plugin.text().of(sender, "exported-database", file.toString()).send(); - } catch(SQLException | IOException e) { + } catch(final SQLException | IOException e) { plugin.logger().warn("Exporting database failed.", e); plugin.text().of(sender, "exporting-failed", e.getMessage()).send(); } }); } - -} +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java index 8778afc1af..8c1c6a97ec 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java @@ -4,6 +4,7 @@ import com.ghostchu.quickshop.api.command.CommandHandler; import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; +import com.ghostchu.quickshop.database.QuickShopCsvTransfer; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; import com.ghostchu.quickshop.util.Util; import com.ghostchu.quickshop.util.logger.Log; @@ -11,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import java.io.File; +import java.io.IOException; import java.sql.SQLException; public class SubCommand_Recovery implements CommandHandler { @@ -47,13 +49,14 @@ public void onCommand(@NotNull final ConsoleCommandSender sender, @NotNull final Util.asyncThreadRun(()->{ try { databaseIOUtil.performBackup("recovery"); - databaseIOUtil.importTables(file); + QuickShopCsvTransfer.importTablesFromZip(file, true); + //databaseIOUtil.importTables(file); Log.debug("Re-loading shop from database..."); Util.mainThreadRun(()->{ plugin.getShopLoader().loadShops(); plugin.text().of(sender, "imported-database", "recovery.zip").send(); }); - } catch(SQLException | ClassNotFoundException e) { + } catch(final SQLException | IOException | ClassNotFoundException e) { plugin.text().of(sender, "importing-failed", e.getMessage()).send(); plugin.logger().warn("Failed to import the database from backup file.", e); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DataTables.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DataTables.java index 1e584e766f..0f2aa99d41 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DataTables.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DataTables.java @@ -218,6 +218,10 @@ private void create(@NotNull final SQLManager sqlManager, @NotNull final String return this.prefix + this.name; } + @NotNull String logicalName() { + return this.name; + } + public @NotNull DeleteBuilder createDelete() { return this.createDelete(this.manager); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java new file mode 100644 index 0000000000..abb6d5b150 --- /dev/null +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java @@ -0,0 +1,277 @@ +package com.ghostchu.quickshop.database; + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import cc.carm.lib.easysql.api.SQLQuery; +import com.ghostchu.quickshop.util.Util; +import com.ghostchu.quickshop.util.logger.Log; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.relique.jdbc.csv.CsvDriver; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * Utility class for transferring DataTables to and from CSV files in a zip format. Provides methods + * to export all tables to a zip file and to import them back. This class is not meant to be + * instantiated. + */ +public final class QuickShopCsvTransfer { + + private QuickShopCsvTransfer() { } + + /** + * Export all DataTables to a zip file. + * + * Each entry: .csv (physicalTableName = table.getName()). + */ + public static void exportTablesToZip(@NotNull final File zipFile) throws SQLException, IOException { + + final File parent = zipFile.getParentFile(); + if(parent != null) parent.mkdirs(); + + if(!zipFile.exists()) { + //noinspection ResultOfMethodCallIgnored + zipFile.createNewFile(); + } + + try(final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { + for(final DataTables table : DataTables.values()) { + final String physicalName = table.getName(); + final long startNs = System.nanoTime(); + + Log.debug("[Backup] Exporting table " + table.name() + " (" + physicalName + ")"); + + // temp csv file + final File tableCsv = new File(Util.getCacheFolder(), physicalName + ".csv"); + //noinspection ResultOfMethodCallIgnored + tableCsv.getParentFile().mkdirs(); + if(tableCsv.exists()) { + //noinspection ResultOfMethodCallIgnored + tableCsv.delete(); + } + //noinspection ResultOfMethodCallIgnored + tableCsv.deleteOnExit(); + + final long rows; + try(final SQLQuery query = table.createQuery().build().execute()) { + final ResultSet result = query.getResultSet(); + rows = writeToCSV(result, tableCsv, physicalName); + } + + Log.debug("[Backup] Adding " + physicalName + ".csv to zip"); + out.putNextEntry(new ZipEntry(physicalName + ".csv")); + try(final InputStream in = new BufferedInputStream(new FileInputStream(tableCsv))) { + in.transferTo(out); + } + out.closeEntry(); + + final long ms = (System.nanoTime() - startNs) / 1_000_000L; + Log.debug("[Backup] Exported " + physicalName + " rows=" + rows + " timeMs=" + ms); + } + } + } + + /** + * Import all tables from a zip created by exportTablesToZip(). + * + * @param purgeBeforeImport if true, purges each table before importing its rows. + */ + public static void importTablesFromZip(@NotNull final File zipFile, + final boolean purgeBeforeImport) throws SQLException, IOException, ClassNotFoundException { + + if(!zipFile.exists()) { + throw new FileNotFoundException("Zip file not found: " + zipFile.getAbsolutePath()); + } + + Log.debug("[Restore] Loading CsvDriver..."); + Class.forName("org.relique.jdbc.csv.CsvDriver"); + + Log.debug("[Restore] Import source: " + zipFile.getAbsolutePath()); + + for(final DataTables table : DataTables.values()) { + final String physicalName = table.getName(); + final long startNs = System.nanoTime(); + + if(purgeBeforeImport) { + Log.debug("[Restore] Purging table " + table.name() + " (" + physicalName + ")"); + table.purgeTable(); + } + + Log.debug("[Restore] Importing table " + table.name() + " (" + physicalName + ")"); + + final long rows = importSingleTableFromZip(zipFile, table); + + final long ms = (System.nanoTime() - startNs) / 1_000_000L; + Log.debug("[Restore] Imported " + physicalName + " rows=" + rows + " timeMs=" + ms); + } + } + + /** + * Import a single table from the zip file using CsvDriver. + * + * Returns number of imported rows. + */ + private static long importSingleTableFromZip(@NotNull final File zipFile, + @NotNull final DataTables table) throws SQLException { + + final String physicalName = table.getName(); + + final String csvJdbcUrl = "jdbc:relique:csv:zip:" + zipFile; + final String querySql = "SELECT * FROM \"" + physicalName + "\""; // quote to handle underscores/prefix safely + + try(final Connection conn = DriverManager.getConnection(csvJdbcUrl); + final Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + final ResultSet results = stmt.executeQuery(querySql)) { + + final ResultSetMetaData metaData = results.getMetaData(); + final int colCount = metaData.getColumnCount(); + final String[] columns = new String[colCount]; + + for(int i = 0; i < colCount; i++) { + columns[i] = metaData.getColumnName(i + 1); + } + + Log.debug("[Restore] Parsed " + columns.length + " columns for " + physicalName + ": " + Arrays.toString(columns)); + + long rows = 0; + + while(results.next()) { + rows++; + + final Object[] values = new Object[colCount]; + for(int i = 0; i < colCount; i++) { + final Object v = results.getObject(columns[i]); + + values[i] = normalizeCsvValue(v); + } + + table.createInsert() + .setColumnNames(columns) + .setParams(values) + .execute(); + } + + return rows; + } catch(final SQLException ex) { + // If the zip is missing a CSV entry, CsvDriver will throw "table not found". + Log.debug("[Restore] Failed importing " + physicalName + ": " + ex.getMessage()); + throw ex; + } + } + + /** + * Writes ResultSet to CSV using CsvDriver, with a small normalization pass to keep BIT(1) + * consistent across drivers (0/1). + * + * Returns number of data rows written. + */ + private static long writeToCSV(@NotNull final ResultSet set, + @NotNull final File csvFile, + @Nullable final String tableNameForLog) throws SQLException, IOException { + + if(!csvFile.getParentFile().exists()) { + //noinspection ResultOfMethodCallIgnored + csvFile.getParentFile().mkdirs(); + } + if(!csvFile.exists()) { + //noinspection ResultOfMethodCallIgnored + csvFile.createNewFile(); + } + + try(final PrintStream stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(csvFile)), false, StandardCharsets.UTF_8)) { + Log.debug("[Backup] Writing CSV: " + (tableNameForLog == null? csvFile.getName() : tableNameForLog) + " -> " + csvFile.getAbsolutePath()); + CsvDriver.writeToCsv(set, stream, true); + } + + // Count rows in the file (header excluded) for progress logging + long rows = 0; + try(final BufferedReader r = Files.newBufferedReader(csvFile.toPath(), StandardCharsets.UTF_8)) { + String line; + boolean first = true; + while((line = r.readLine()) != null) { + if(first) { + first = false; + continue; + } + if(!line.isEmpty()) rows++; + } + } + return rows; + } + + /** + * Normalize odd CSV representations that can appear depending on drivers. Keeps behavior + * backwards compatible with your working importer. + */ + private static Object normalizeCsvValue(final Object v) { + + if(v == null) return null; + + if(v instanceof final byte[] bytes) { + // BIT(1) from some sources + boolean on = false; + for(final byte b : bytes) { + if(b != 0) { + on = true; + break; + } + } + return on? "1" : "0"; + } + + if(v instanceof final String s) { + final String t = s.trim(); + // MySQL BIT literals sometimes look like: b'0' / b'1' + if(t.startsWith("b'") && t.endsWith("'") && t.length() == 4) { + final char c = t.charAt(2); + if(c == '0' || c == '1') return String.valueOf(c); + } + // common boolean strings + if(t.equalsIgnoreCase("true")) return "1"; + if(t.equalsIgnoreCase("false")) return "0"; + // NULL token some tools use + if(t.equals("\\N")) return null; + + return s; + } + + return v; + } +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java index 48af926cf4..2bb5e0b14d 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java @@ -500,6 +500,11 @@ public boolean actionSelling(@NotNull final Player seller, @NotNull final Invent if(stock == -1) { stock = 10000; } + + /*if(shop.isStackingShop()) { + stock = stock * shop.getItem().getAmount(); + }*/ + if(stock < amount) { plugin.text().of(seller, "shop-stock-too-low", Component.text(stock), Util.getItemStackName(shop.getItem())).send(); return false; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ShopUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ShopUtil.java index 51ca149fdc..52d4814500 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ShopUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ShopUtil.java @@ -246,7 +246,8 @@ public static boolean sellToShop(@NotNull final Player p, @Nullable final Shop s if(all) { arg = buyingShopAllCalc(eco, shop, p); } else { - arg = shop.getShopStackingAmount(); + //if it's not all, it's one + arg = 1; } if(arg == 0) { return true; @@ -271,7 +272,8 @@ public static boolean buyFromShop(@NotNull final Player p, @Nullable final Shop if(all) { arg = sellingShopAllCalc(eco, shop, p); } else { - arg = shop.getShopStackingAmount(); + //if it's not all, it's one + arg = 1; } if(arg == 0) { diff --git a/quickshop-bukkit/src/main/resources/interaction.yml b/quickshop-bukkit/src/main/resources/interaction.yml index cd5297a439..57e2913fb6 100644 --- a/quickshop-bukkit/src/main/resources/interaction.yml +++ b/quickshop-bukkit/src/main/resources/interaction.yml @@ -1,10 +1,14 @@ -version: 1 +version: 2 # Chat Mode: STANDING_LEFT_CLICK_SIGN: TRADE_INTERACTION STANDING_RIGHT_CLICK_SIGN: CONTROL_PANEL STANDING_LEFT_CLICK_SHOPBLOCK: TRADE_INTERACTION STANDING_RIGHT_CLICK_SHOPBLOCK: NONE +STANDING_LEFT_CLICK_CONTAINER: TRADE_INTERACTION +STANDING_RIGHT_CLICK_CONTAINER: NONE +SNEAKING_LEFT_CLICK_CONTAINER: NONE +SNEAKING_RIGHT_CLICK_CONTAINER: NONE SNEAKING_LEFT_CLICK_SIGN: NONE SNEAKING_RIGHT_CLICK_SIGN: NONE SNEAKING_LEFT_CLICK_SHOPBLOCK: NONE From 71c126b4a61e0afcbbc0c849d67cec87f8372ec5 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sat, 20 Dec 2025 20:24:37 -0500 Subject: [PATCH 03/19] Remove `QuickShopCsvTransfer` and refactor database import/export logic to utilize `DatabaseIOUtil`. Simplify related subcommands and update CSV handling methods. --- .../command/subcommand/SubCommand_Export.java | 4 +- .../subcommand/SubCommand_Recovery.java | 7 +- .../quickshop/database/DatabaseIOUtil.java | 30 +- .../database/QuickShopCsvTransfer.java | 277 ------------------ 4 files changed, 23 insertions(+), 295 deletions(-) delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java index 23a14a81c6..ef12fc7761 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java @@ -4,7 +4,6 @@ import com.ghostchu.quickshop.api.command.CommandHandler; import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; -import com.ghostchu.quickshop.database.QuickShopCsvTransfer; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; import com.ghostchu.quickshop.util.Util; import org.bukkit.command.ConsoleCommandSender; @@ -32,8 +31,7 @@ public synchronized void onCommand(@NotNull final ConsoleCommandSender sender, @ final DatabaseIOUtil databaseIOUtil = new DatabaseIOUtil((SimpleDatabaseHelperV2)plugin.getDatabaseHelper()); Util.asyncThreadRun(()->{ try { - //databaseIOUtil.exportTables(file); - QuickShopCsvTransfer.exportTablesToZip(file); + databaseIOUtil.exportTables(file); plugin.text().of(sender, "exported-database", file.toString()).send(); } catch(final SQLException | IOException e) { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java index 8c1c6a97ec..b0468ffd90 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java @@ -4,7 +4,6 @@ import com.ghostchu.quickshop.api.command.CommandHandler; import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; -import com.ghostchu.quickshop.database.QuickShopCsvTransfer; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; import com.ghostchu.quickshop.util.Util; import com.ghostchu.quickshop.util.logger.Log; @@ -12,7 +11,6 @@ import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.IOException; import java.sql.SQLException; public class SubCommand_Recovery implements CommandHandler { @@ -49,14 +47,13 @@ public void onCommand(@NotNull final ConsoleCommandSender sender, @NotNull final Util.asyncThreadRun(()->{ try { databaseIOUtil.performBackup("recovery"); - QuickShopCsvTransfer.importTablesFromZip(file, true); - //databaseIOUtil.importTables(file); + databaseIOUtil.importTables(file); Log.debug("Re-loading shop from database..."); Util.mainThreadRun(()->{ plugin.getShopLoader().loadShops(); plugin.text().of(sender, "imported-database", "recovery.zip").send(); }); - } catch(final SQLException | IOException | ClassNotFoundException e) { + } catch(final SQLException | ClassNotFoundException e) { plugin.text().of(sender, "importing-failed", e.getMessage()).send(); plugin.logger().warn("Failed to import the database from backup file.", e); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java index a985b4a636..1944bcb7e7 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java @@ -60,11 +60,11 @@ public boolean performBackup(final String reason) { try { exportTables(backupFile); return true; - } catch(SQLException | IOException e) { + } catch(final SQLException | IOException e) { QuickShop.getInstance().logger().warn("[DB Backup] Failed to create backup", e); return false; } - } catch(Throwable throwable) { + } catch(final Throwable throwable) { QuickShop.getInstance().logger().warn("[DB Backup] Unexpected error", throwable); return false; } @@ -73,12 +73,12 @@ public boolean performBackup(final String reason) { public void exportTables(@NotNull final File zipFile) throws SQLException, IOException { // zipFile.getParentFile().mkdirs(); zipFile.createNewFile(); - try(ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) { + try(final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) { for(final DataTables table : DataTables.values()) { Log.debug("Exporting table " + table.name()); final File tableCsv = new File(Util.getCacheFolder(), table.getName() + ".csv"); tableCsv.deleteOnExit(); - try(SQLQuery query = table.createQuery().build().execute()) { + try(final SQLQuery query = table.createQuery().build().execute()) { final ResultSet result = query.getResultSet(); writeToCSV(result, tableCsv); Log.debug("Exported table " + table.name() + " to " + tableCsv.getAbsolutePath()); @@ -107,10 +107,10 @@ public void importFromCSV(@NotNull final File zipFile, @NotNull final DataTables Log.debug("Loading CsvDriver..."); Class.forName("org.relique.jdbc.csv.CsvDriver"); - try(Connection conn = DriverManager.getConnection("jdbc:relique:csv:zip:" + zipFile); - Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, - ResultSet.CONCUR_READ_ONLY); - ResultSet results = stmt.executeQuery("SELECT * FROM " + table.getName())) { + try(final Connection conn = DriverManager.getConnection("jdbc:relique:csv:zip:" + zipFile); + final Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.CONCUR_READ_ONLY); + final ResultSet results = stmt.executeQuery("SELECT * FROM " + table.logicalName())) { final ResultSetMetaData metaData = results.getMetaData(); final String[] columns = new String[metaData.getColumnCount()]; for(int i = 0; i < columns.length; i++) { @@ -118,9 +118,19 @@ public void importFromCSV(@NotNull final File zipFile, @NotNull final DataTables } Log.debug("Parsed " + columns.length + " columns: " + CommonUtil.array2String(columns)); while(results.next()) { - final Object[] values = new String[columns.length]; + final Object[] values = new Object[columns.length]; for(int i = 0; i < values.length; i++) { Log.debug("Copying column: " + columns[i]); + + final Object obj = results.getObject(columns[i]); + //System.out.println("checking obj: " + columns[i] + " = " + obj.toString()); + if(columns[i].equalsIgnoreCase("unlimited") || columns[i].equalsIgnoreCase("hologram")) { + + //System.out.println("result is boolean"); + values[i] = obj.toString().equalsIgnoreCase("TRUE"); + continue; + } + values[i] = results.getObject(columns[i]); } Log.debug("Inserting row: " + CommonUtil.array2String(Arrays.stream(values).map(Object::toString).toArray(String[]::new))); @@ -140,7 +150,7 @@ public void writeToCSV(@NotNull final ResultSet set, @NotNull final File csvFile if(!csvFile.exists()) { csvFile.createNewFile(); } - try(PrintStream stream = new PrintStream(csvFile)) { + try(final PrintStream stream = new PrintStream(csvFile)) { Log.debug("Writing to CSV file: " + csvFile.getAbsolutePath()); CsvDriver.writeToCsv(set, stream, true); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java deleted file mode 100644 index abb6d5b150..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/QuickShopCsvTransfer.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.ghostchu.quickshop.database; - -/* - * QuickShop-Hikari - * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import cc.carm.lib.easysql.api.SQLQuery; -import com.ghostchu.quickshop.util.Util; -import com.ghostchu.quickshop.util.logger.Log; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.relique.jdbc.csv.CsvDriver; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Arrays; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -/** - * Utility class for transferring DataTables to and from CSV files in a zip format. Provides methods - * to export all tables to a zip file and to import them back. This class is not meant to be - * instantiated. - */ -public final class QuickShopCsvTransfer { - - private QuickShopCsvTransfer() { } - - /** - * Export all DataTables to a zip file. - * - * Each entry: .csv (physicalTableName = table.getName()). - */ - public static void exportTablesToZip(@NotNull final File zipFile) throws SQLException, IOException { - - final File parent = zipFile.getParentFile(); - if(parent != null) parent.mkdirs(); - - if(!zipFile.exists()) { - //noinspection ResultOfMethodCallIgnored - zipFile.createNewFile(); - } - - try(final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { - for(final DataTables table : DataTables.values()) { - final String physicalName = table.getName(); - final long startNs = System.nanoTime(); - - Log.debug("[Backup] Exporting table " + table.name() + " (" + physicalName + ")"); - - // temp csv file - final File tableCsv = new File(Util.getCacheFolder(), physicalName + ".csv"); - //noinspection ResultOfMethodCallIgnored - tableCsv.getParentFile().mkdirs(); - if(tableCsv.exists()) { - //noinspection ResultOfMethodCallIgnored - tableCsv.delete(); - } - //noinspection ResultOfMethodCallIgnored - tableCsv.deleteOnExit(); - - final long rows; - try(final SQLQuery query = table.createQuery().build().execute()) { - final ResultSet result = query.getResultSet(); - rows = writeToCSV(result, tableCsv, physicalName); - } - - Log.debug("[Backup] Adding " + physicalName + ".csv to zip"); - out.putNextEntry(new ZipEntry(physicalName + ".csv")); - try(final InputStream in = new BufferedInputStream(new FileInputStream(tableCsv))) { - in.transferTo(out); - } - out.closeEntry(); - - final long ms = (System.nanoTime() - startNs) / 1_000_000L; - Log.debug("[Backup] Exported " + physicalName + " rows=" + rows + " timeMs=" + ms); - } - } - } - - /** - * Import all tables from a zip created by exportTablesToZip(). - * - * @param purgeBeforeImport if true, purges each table before importing its rows. - */ - public static void importTablesFromZip(@NotNull final File zipFile, - final boolean purgeBeforeImport) throws SQLException, IOException, ClassNotFoundException { - - if(!zipFile.exists()) { - throw new FileNotFoundException("Zip file not found: " + zipFile.getAbsolutePath()); - } - - Log.debug("[Restore] Loading CsvDriver..."); - Class.forName("org.relique.jdbc.csv.CsvDriver"); - - Log.debug("[Restore] Import source: " + zipFile.getAbsolutePath()); - - for(final DataTables table : DataTables.values()) { - final String physicalName = table.getName(); - final long startNs = System.nanoTime(); - - if(purgeBeforeImport) { - Log.debug("[Restore] Purging table " + table.name() + " (" + physicalName + ")"); - table.purgeTable(); - } - - Log.debug("[Restore] Importing table " + table.name() + " (" + physicalName + ")"); - - final long rows = importSingleTableFromZip(zipFile, table); - - final long ms = (System.nanoTime() - startNs) / 1_000_000L; - Log.debug("[Restore] Imported " + physicalName + " rows=" + rows + " timeMs=" + ms); - } - } - - /** - * Import a single table from the zip file using CsvDriver. - * - * Returns number of imported rows. - */ - private static long importSingleTableFromZip(@NotNull final File zipFile, - @NotNull final DataTables table) throws SQLException { - - final String physicalName = table.getName(); - - final String csvJdbcUrl = "jdbc:relique:csv:zip:" + zipFile; - final String querySql = "SELECT * FROM \"" + physicalName + "\""; // quote to handle underscores/prefix safely - - try(final Connection conn = DriverManager.getConnection(csvJdbcUrl); - final Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - final ResultSet results = stmt.executeQuery(querySql)) { - - final ResultSetMetaData metaData = results.getMetaData(); - final int colCount = metaData.getColumnCount(); - final String[] columns = new String[colCount]; - - for(int i = 0; i < colCount; i++) { - columns[i] = metaData.getColumnName(i + 1); - } - - Log.debug("[Restore] Parsed " + columns.length + " columns for " + physicalName + ": " + Arrays.toString(columns)); - - long rows = 0; - - while(results.next()) { - rows++; - - final Object[] values = new Object[colCount]; - for(int i = 0; i < colCount; i++) { - final Object v = results.getObject(columns[i]); - - values[i] = normalizeCsvValue(v); - } - - table.createInsert() - .setColumnNames(columns) - .setParams(values) - .execute(); - } - - return rows; - } catch(final SQLException ex) { - // If the zip is missing a CSV entry, CsvDriver will throw "table not found". - Log.debug("[Restore] Failed importing " + physicalName + ": " + ex.getMessage()); - throw ex; - } - } - - /** - * Writes ResultSet to CSV using CsvDriver, with a small normalization pass to keep BIT(1) - * consistent across drivers (0/1). - * - * Returns number of data rows written. - */ - private static long writeToCSV(@NotNull final ResultSet set, - @NotNull final File csvFile, - @Nullable final String tableNameForLog) throws SQLException, IOException { - - if(!csvFile.getParentFile().exists()) { - //noinspection ResultOfMethodCallIgnored - csvFile.getParentFile().mkdirs(); - } - if(!csvFile.exists()) { - //noinspection ResultOfMethodCallIgnored - csvFile.createNewFile(); - } - - try(final PrintStream stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(csvFile)), false, StandardCharsets.UTF_8)) { - Log.debug("[Backup] Writing CSV: " + (tableNameForLog == null? csvFile.getName() : tableNameForLog) + " -> " + csvFile.getAbsolutePath()); - CsvDriver.writeToCsv(set, stream, true); - } - - // Count rows in the file (header excluded) for progress logging - long rows = 0; - try(final BufferedReader r = Files.newBufferedReader(csvFile.toPath(), StandardCharsets.UTF_8)) { - String line; - boolean first = true; - while((line = r.readLine()) != null) { - if(first) { - first = false; - continue; - } - if(!line.isEmpty()) rows++; - } - } - return rows; - } - - /** - * Normalize odd CSV representations that can appear depending on drivers. Keeps behavior - * backwards compatible with your working importer. - */ - private static Object normalizeCsvValue(final Object v) { - - if(v == null) return null; - - if(v instanceof final byte[] bytes) { - // BIT(1) from some sources - boolean on = false; - for(final byte b : bytes) { - if(b != 0) { - on = true; - break; - } - } - return on? "1" : "0"; - } - - if(v instanceof final String s) { - final String t = s.trim(); - // MySQL BIT literals sometimes look like: b'0' / b'1' - if(t.startsWith("b'") && t.endsWith("'") && t.length() == 4) { - final char c = t.charAt(2); - if(c == '0' || c == '1') return String.valueOf(c); - } - // common boolean strings - if(t.equalsIgnoreCase("true")) return "1"; - if(t.equalsIgnoreCase("false")) return "0"; - // NULL token some tools use - if(t.equals("\\N")) return null; - - return s; - } - - return v; - } -} \ No newline at end of file From fe7cc551d0401898ef06721020ccc15dea30c7c4 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Tue, 23 Dec 2025 17:39:55 -0500 Subject: [PATCH 04/19] Corrected issue where the max stack size wasn't being followed on shop creation. --- .../command/subcommand/SubCommand_Create.java | 6 ++++++ .../quickshop/command/subcommand/SubCommand_Size.java | 3 ++- .../ghostchu/quickshop/database/DatabaseIOUtil.java | 10 +--------- .../main/java/com/ghostchu/quickshop/util/Util.java | 6 ++++++ 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Create.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Create.java index 253758d796..4ee46d43f3 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Create.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Create.java @@ -56,6 +56,12 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman if(amount < 1) { amount = 1; } + + final int maxSize = Util.getItemMaxStackSize(material); + if(amount > maxSize) { + amount = maxSize; + } + item = new ItemStack(material, amount); } catch(final NumberFormatException e) { item = new ItemStack(material, 1); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Size.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Size.java index 3ce2313ce3..b8a4e9ef98 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Size.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Size.java @@ -37,7 +37,7 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman final int amount; try { amount = Integer.parseInt(parser.getArgs().getFirst()); - } catch(NumberFormatException e) { + } catch(final NumberFormatException e) { plugin.text().of(sender, "not-a-integer", parser.getArgs().getFirst()).send(); return; } @@ -49,6 +49,7 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman plugin.text().of(sender, "command.invalid-bulk-amount", amount).send(); return; } + if(amount > Util.getItemMaxStackSize(shop.getItem().getType()) && !plugin.getConfig().getBoolean("shop.disable-max-size-check-for-size-command", false)) { plugin.text().of(sender, "command.invalid-bulk-amount", amount).send(); return; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java index 1944bcb7e7..4149f72502 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java @@ -118,18 +118,10 @@ public void importFromCSV(@NotNull final File zipFile, @NotNull final DataTables } Log.debug("Parsed " + columns.length + " columns: " + CommonUtil.array2String(columns)); while(results.next()) { - final Object[] values = new Object[columns.length]; + final Object[] values = new String[columns.length]; for(int i = 0; i < values.length; i++) { Log.debug("Copying column: " + columns[i]); - final Object obj = results.getObject(columns[i]); - //System.out.println("checking obj: " + columns[i] + " = " + obj.toString()); - if(columns[i].equalsIgnoreCase("unlimited") || columns[i].equalsIgnoreCase("hologram")) { - - //System.out.println("result is boolean"); - values[i] = obj.toString().equalsIgnoreCase("TRUE"); - continue; - } values[i] = results.getObject(columns[i]); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java index 585b2437ef..a3bc511ad5 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java @@ -166,6 +166,12 @@ public static boolean createShop(@NotNull final Player player, @Nullable final B } final ItemStack stack = item.clone(); + + final int maxSize = Util.getItemMaxStackSize(stack.getType()); + if(stack.getAmount() > maxSize) { + stack.setAmount(maxSize); + } + if(stack.getType().isAir()) { Log.debug("Invalid trade item: air"); return false; // Air cannot be used for trade From 1f45f1556a57584c5e2bdaf29b6e67ad69ec7d3e Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Tue, 23 Dec 2025 19:03:10 -0500 Subject: [PATCH 05/19] Refactor database export/import logic: introduce `TableZipCsvBackup`, update subcommands to utilize the new utility, and simplify CSV handling processes. --- .../command/subcommand/SubCommand_Export.java | 3 +- .../subcommand/SubCommand_Recovery.java | 6 +- .../quickshop/database/DatabaseIOUtil.java | 5 +- .../quickshop/database/TableZipCsvBackup.java | 309 ++++++++++++++++++ 4 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/TableZipCsvBackup.java diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java index ef12fc7761..111a851647 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Export.java @@ -5,6 +5,7 @@ import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; +import com.ghostchu.quickshop.database.TableZipCsvBackup; import com.ghostchu.quickshop.util.Util; import org.bukkit.command.ConsoleCommandSender; import org.jetbrains.annotations.NotNull; @@ -31,7 +32,7 @@ public synchronized void onCommand(@NotNull final ConsoleCommandSender sender, @ final DatabaseIOUtil databaseIOUtil = new DatabaseIOUtil((SimpleDatabaseHelperV2)plugin.getDatabaseHelper()); Util.asyncThreadRun(()->{ try { - databaseIOUtil.exportTables(file); + TableZipCsvBackup.exportTables(file); plugin.text().of(sender, "exported-database", file.toString()).send(); } catch(final SQLException | IOException e) { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java index b0468ffd90..24dd3fcf3b 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java @@ -5,12 +5,14 @@ import com.ghostchu.quickshop.api.command.CommandParser; import com.ghostchu.quickshop.database.DatabaseIOUtil; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; +import com.ghostchu.quickshop.database.TableZipCsvBackup; import com.ghostchu.quickshop.util.Util; import com.ghostchu.quickshop.util.logger.Log; import org.bukkit.command.ConsoleCommandSender; import org.jetbrains.annotations.NotNull; import java.io.File; +import java.io.IOException; import java.sql.SQLException; public class SubCommand_Recovery implements CommandHandler { @@ -47,13 +49,13 @@ public void onCommand(@NotNull final ConsoleCommandSender sender, @NotNull final Util.asyncThreadRun(()->{ try { databaseIOUtil.performBackup("recovery"); - databaseIOUtil.importTables(file); + TableZipCsvBackup.importTables(file); Log.debug("Re-loading shop from database..."); Util.mainThreadRun(()->{ plugin.getShopLoader().loadShops(); plugin.text().of(sender, "imported-database", "recovery.zip").send(); }); - } catch(final SQLException | ClassNotFoundException e) { + } catch(final SQLException | IOException | ClassNotFoundException e) { plugin.text().of(sender, "importing-failed", e.getMessage()).send(); plugin.logger().warn("Failed to import the database from backup file.", e); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java index 4149f72502..775c6588c4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/DatabaseIOUtil.java @@ -108,9 +108,10 @@ public void importFromCSV(@NotNull final File zipFile, @NotNull final DataTables Log.debug("Loading CsvDriver..."); Class.forName("org.relique.jdbc.csv.CsvDriver"); try(final Connection conn = DriverManager.getConnection("jdbc:relique:csv:zip:" + zipFile); - final Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, + final Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); - final ResultSet results = stmt.executeQuery("SELECT * FROM " + table.logicalName())) { + final ResultSet results = stmt.executeQuery("SELECT * FROM " + table.logicalName())) { + final ResultSetMetaData metaData = results.getMetaData(); final String[] columns = new String[metaData.getColumnCount()]; for(int i = 0; i < columns.length; i++) { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/TableZipCsvBackup.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/TableZipCsvBackup.java new file mode 100644 index 0000000000..541e16a562 --- /dev/null +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/TableZipCsvBackup.java @@ -0,0 +1,309 @@ +package com.ghostchu.quickshop.database; + +import cc.carm.lib.easysql.api.SQLQuery; +import com.ghostchu.quickshop.common.util.CommonUtil; +import com.ghostchu.quickshop.util.Util; +import com.ghostchu.quickshop.util.logger.Log; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.jetbrains.annotations.NotNull; +import org.relique.jdbc.csv.CsvDriver; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * TableZipCsvBackup + * + * @author creatorfromhell + * @since 6.2.0.11 + */ +public final class TableZipCsvBackup { + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + private static TableSchema readSchemaFromZip(@NotNull final File zipFile, @NotNull final String tableName) + throws IOException { + + final String entryName = tableName + ".schema.json"; + try(final ZipFile zf = new ZipFile(zipFile)) { + final ZipEntry entry = zf.getEntry(entryName); + if(entry == null) return null; + + try(final InputStream in = zf.getInputStream(entry); + final InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { + return GSON.fromJson(reader, TableSchema.class); + } + } + } + + private static Object convertCsvString(final String raw, final ColumnSchema col) throws SQLException { + + if(raw == null) return null; + final String s = raw.trim(); + if(s.isEmpty()) return null; + + try { + switch(col.sqlType) { + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + return Integer.valueOf(s); + + case Types.BIGINT: + return Long.valueOf(s); + + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + return Double.valueOf(s); + + case Types.DECIMAL: + case Types.NUMERIC: + return new BigDecimal(s); + + case Types.BIT: + case Types.BOOLEAN: + if(s.equalsIgnoreCase("true") || s.equalsIgnoreCase("yes")) return true; + if(s.equalsIgnoreCase("false") || s.equalsIgnoreCase("no")) return false; + if(s.equals("1")) return true; + if(s.equals("0")) return false; + return Boolean.valueOf(s); + + case Types.DATE: + return Date.valueOf(s); + + case Types.TIME: + return Time.valueOf(s); + + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + return parseTimestampFlexible(s); + + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + default: + return s; + } + } catch(final Exception ex) { + + throw new SQLException("Failed to convert CSV value '" + s + "' for column '" + col.name + + "' (sqlType=" + col.sqlType + ", typeName=" + col.typeName + ")", ex); + } + } + + private static Timestamp parseTimestampFlexible(final String s) { + try { + final Instant inst = Instant.parse(s); + return Timestamp.from(inst); + } catch(final Exception ignored) { } + + try { + final OffsetDateTime odt = OffsetDateTime.parse(s); + return Timestamp.from(odt.toInstant()); + } catch(final Exception ignored) { } + + try { + final LocalDateTime ldt = LocalDateTime.parse(s); + return Timestamp.valueOf(ldt); + } catch(final Exception ignored) { } + + return Timestamp.valueOf(s); + } + + public static void exportTables(@NotNull final File zipFile) throws SQLException, IOException { + + zipFile.getParentFile().mkdirs(); + if(!zipFile.exists()) zipFile.createNewFile(); + + try(final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) { + for(final DataTables table : DataTables.values()) { + Log.debug("Exporting table " + table.name()); + + final File tableCsv = new File(Util.getCacheFolder(), table.getName() + ".csv"); + tableCsv.getParentFile().mkdirs(); + if(tableCsv.exists()) tableCsv.delete(); + tableCsv.deleteOnExit(); + + final TableSchema schema; + try(final SQLQuery query = table.createQuery().build().execute()) { + final ResultSet rs = query.getResultSet(); + + writeToCSV(rs, tableCsv); + + schema = TableSchema.from(table.getName(), rs.getMetaData()); + + Log.debug("Exported table " + table.name() + " to " + tableCsv.getAbsolutePath()); + } + + Log.debug("Adding CSV for " + table.name() + " to zip file"); + out.putNextEntry(new ZipEntry(table.getName() + ".csv")); + Files.copy(tableCsv.toPath(), out); + out.closeEntry(); + + Log.debug("Adding schema for " + table.name() + " to zip file"); + out.putNextEntry(new ZipEntry(table.getName() + ".schema.json")); + final byte[] schemaBytes = GSON.toJson(schema).getBytes(StandardCharsets.UTF_8); + out.write(schemaBytes); + out.closeEntry(); + + Log.debug("Added table " + table.name() + " to zip file"); + } + } + } + + public static void importTables(@NotNull final File zipFile) throws SQLException, ClassNotFoundException, IOException { + + for(final DataTables table : DataTables.values()) { + Log.debug("Purging table " + table.getName()); + table.purgeTable(); + + Log.debug("Importing table " + table.getName() + " from " + zipFile.getAbsolutePath()); + importFromCSV(zipFile, table); + Log.debug("Imported table " + table.getName() + " from " + zipFile.getAbsolutePath()); + } + } + + public static void importFromCSV(@NotNull final File zipFile, @NotNull final DataTables table) + throws SQLException, ClassNotFoundException, IOException { + + final TableSchema schema = readSchemaFromZip(zipFile, table.getName()); + if(schema == null) { + throw new IllegalStateException("Missing schema sidecar for table " + table.getName() + + " (expected " + table.getName() + ".schema.json in zip)"); + } + + Log.debug("Loading CsvDriver..."); + Class.forName("org.relique.jdbc.csv.CsvDriver"); + + final String csvTableName = table.getName(); + + try(final Connection conn = DriverManager.getConnection("jdbc:relique:csv:zip:" + zipFile); + final Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet results = stmt.executeQuery("SELECT * FROM " + csvTableName)) { + + final String[] columnNames = schema.columnNames(); + Log.debug("Schema columns (" + columnNames.length + "): " + CommonUtil.array2String(columnNames)); + + while(results.next()) { + final Object[] values = new Object[columnNames.length]; + + for(int i = 0; i < columnNames.length; i++) { + final ColumnSchema col = schema.columns.get(i); + + final String raw = results.getString(col.name); + values[i] = convertCsvString(raw, col); + } + + Log.debug("Inserting row: " + CommonUtil.array2String( + Arrays.stream(values).map(v->v == null? "null" : String.valueOf(v)).toArray(String[]::new) + )); + + table.createInsert() + .setColumnNames(columnNames) + .setParams(values) + .execute(); + } + } + } + + public static void writeToCSV(@NotNull final ResultSet set, @NotNull final File csvFile) throws SQLException, IOException { + + if(!csvFile.getParentFile().exists()) csvFile.getParentFile().mkdirs(); + if(!csvFile.exists()) csvFile.createNewFile(); + + try(final PrintStream stream = new PrintStream(csvFile)) { + Log.debug("Writing to CSV file: " + csvFile.getAbsolutePath()); + CsvDriver.writeToCsv(set, stream, true); + } + } + + public static final class TableSchema { + + public String table; + public List columns = new ArrayList<>(); + + public static TableSchema from(final String tableName, final ResultSetMetaData md) throws SQLException { + + final TableSchema schema = new TableSchema(); + schema.table = tableName; + + for(int i = 1; i <= md.getColumnCount(); i++) { + final ColumnSchema col = new ColumnSchema(); + col.name = md.getColumnLabel(i); // label respects aliases; use getColumnName if you prefer + col.sqlType = md.getColumnType(i); + col.typeName = md.getColumnTypeName(i); + col.nullable = (md.isNullable(i) != ResultSetMetaData.columnNoNulls); + col.precision = md.getPrecision(i); + col.scale = md.getScale(i); + schema.columns.add(col); + } + return schema; + } + + public String[] columnNames() { + + final String[] arr = new String[columns.size()]; + for(int i = 0; i < columns.size(); i++) arr[i] = columns.get(i).name; + return arr; + } + } + + public static final class ColumnSchema { + + public String name; + public int sqlType; + public String typeName; + public boolean nullable; + public int precision; + public int scale; + } +} \ No newline at end of file From c88e89fc6aa8c24d9002a4872128aa4c97aca366 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Tue, 23 Dec 2025 21:29:29 -0500 Subject: [PATCH 06/19] Patch Protocollib for 5.4.0 with undocumented changes. --- pom.xml | 2 +- .../subcommand/SubCommand_Recovery.java | 2 +- .../virtual/packet/ProtocolLibHandler.java | 7 +- .../protocollib/PacketFactoryv1_21_10.java | 305 ++++++++++++++++++ 4 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/protocollib/PacketFactoryv1_21_10.java diff --git a/pom.xml b/pom.xml index 069001f172..e9b2432ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ 2.15.0 2.9.0-SNAPSHOT 1.21-R0.1-SNAPSHOT - 5.3.0 + 5.4.0 1.7.1 2.13 diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java index 24dd3fcf3b..0f03a5c0b0 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Recovery.java @@ -62,4 +62,4 @@ public void onCommand(@NotNull final ConsoleCommandSender sender, @NotNull final }); } -} +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/ProtocolLibHandler.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/ProtocolLibHandler.java index 5d47be0469..2eabac174c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/ProtocolLibHandler.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/ProtocolLibHandler.java @@ -23,6 +23,7 @@ import com.ghostchu.quickshop.api.shop.display.PacketHandler; import com.ghostchu.quickshop.shop.display.virtual.packet.protocollib.PacketFactoryv1_20; import com.ghostchu.quickshop.shop.display.virtual.packet.protocollib.PacketFactoryv1_21; +import com.ghostchu.quickshop.shop.display.virtual.packet.protocollib.PacketFactoryv1_21_10; import java.util.HashMap; import java.util.Map; @@ -109,8 +110,10 @@ public void initialize() { factories.put("1.21.7", oneTwentyOne); factories.put("1.21.8", oneTwentyOne); factories.put("1.21.9", oneTwentyOne); - factories.put("1.21.10", oneTwentyOne); - factories.put("1.21.11", oneTwentyOne); + + final PacketFactoryv1_21_10 oneTwentyOneTen = new PacketFactoryv1_21_10(); + factories.put("1.21.10", oneTwentyOneTen); + factories.put("1.21.11", oneTwentyOneTen); } @Override diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/protocollib/PacketFactoryv1_21_10.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/protocollib/PacketFactoryv1_21_10.java new file mode 100644 index 0000000000..53eda5a44b --- /dev/null +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/display/virtual/packet/protocollib/PacketFactoryv1_21_10.java @@ -0,0 +1,305 @@ +package com.ghostchu.quickshop.shop.display.virtual.packet.protocollib; +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.ChunkCoordIntPair; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.comphenix.protocol.wrappers.WrappedDataValue; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.ghostchu.quickshop.QuickShop; +import com.ghostchu.quickshop.api.shop.display.PacketFactory; +import com.ghostchu.quickshop.shop.SimpleShopChunk; +import com.ghostchu.quickshop.shop.display.virtual.VirtualDisplayItem; +import com.ghostchu.quickshop.shop.display.virtual.VirtualDisplayItemManager; +import com.ghostchu.quickshop.shop.display.virtual.packet.ProtocolLibHandler; +import com.ghostchu.quickshop.util.Util; +import lombok.Getter; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * PacketFactoryv1_21 + * + * @author creatorfromhell + * @since 6.2.0.9 + */ +public class PacketFactoryv1_21_10 implements PacketFactory { + + private static final WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.getItemStackSerializer(false); + + @Getter + private PacketAdapter chunkSendingPacketAdapter; + + @Getter + private PacketAdapter chunkUnloadingPacketAdapter; + + + /** + * Creates a spawn packet for the specified ID and display location. + * + * @param id the ID of the packet to be created + * @param displayLocation the display location where the packet will be spawned + * + * @return the spawn packet of type T + */ + @Override + public PacketContainer createSpawnPacket(final int id, @NotNull final Location displayLocation) { + + final UUID identifier = UUID.nameUUIDFromBytes(("SHOP:" + id).getBytes(StandardCharsets.UTF_8)); + + //First, create a new packet to spawn item + final PacketContainer fakeItemPacket = ProtocolLibHandler.instance().internal().createPacket(PacketType.Play.Server.SPAWN_ENTITY); + + //id and velocity + fakeItemPacket.getIntegers().write(0, id); + fakeItemPacket.getVectors().write(0, new Vector(0, 0, 0)); + + //Entity Type + fakeItemPacket.getEntityTypeModifier().write(0, EntityType.ITEM); + + //UUID + fakeItemPacket.getUUIDs().write(0, identifier); + + //Location + fakeItemPacket.getDoubles() + //X + .write(0, displayLocation.getX()) + //Y + .write(1, displayLocation.getY()) + //Z + .write(2, displayLocation.getZ()); + return fakeItemPacket; + } + + /** + * Creates a metadata packet with the specified ID and ItemStack. + * + * @param id the ID of the metadata packet to be created + * @param itemStack the ItemStack to include in the metadata packet + * + * @return the metadata packet of type T + */ + @Override + public PacketContainer createMetaDataPacket(final int id, @NotNull final ItemStack itemStack) { + + final List values = new ArrayList<>(); + + //gravity disabled + values.add(new WrappedDataValue(5, WrappedDataWatcher.Registry.get(Boolean.class), true)); + values.add(new WrappedDataValue(8, serializer, MinecraftReflection.getMinecraftItemStack(itemStack))); + + if(QuickShop.getInstance().getVirtualDisplayItemManager().useItemName()) { + + final String itemName = GsonComponentSerializer.gson().serialize(Util.getItemStackName(itemStack)); + + values.add(new WrappedDataValue(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true), Optional.of(WrappedChatComponent.fromJson(itemName).getHandle()))); + values.add(new WrappedDataValue(3, WrappedDataWatcher.Registry.get(Boolean.class), true)); + } + + //Next, create a new packet to update item data (default is empty) + final PacketContainer fakeItemMetaPacket = ProtocolLibHandler.instance().internal().createPacket(PacketType.Play.Server.ENTITY_METADATA); + //Entity ID + fakeItemMetaPacket.getIntegers().write(0, id); + fakeItemMetaPacket.getDataValueCollectionModifier().write(0, values); + + //Add it + //For 1.19.2+, we need to use DataValue instead of WatchableObject + //Check for new version protocolLib + try { + Class.forName("com.comphenix.protocol.wrappers.WrappedDataValue"); + } catch(final ClassNotFoundException e) { + throw new RuntimeException("Unable to initialize packet, ProtocolLib update needed", e); + } + return fakeItemMetaPacket; + } + + /** + * Creates a velocity packet with the specified ID. + * + * @param id the ID of the velocity packet to be created + * + * @return the velocity packet of type T + */ + @Override + public PacketContainer createVelocityPacket(final int id) { + + return null; + } + + /** + * Creates a destroy packet for the given ID. + * + * @param id the ID of the packet to be destroyed + * + * @return the destroy packet of type T + */ + @Override + public PacketContainer createDestroyPacket(final int id) { + //Also make a DestroyPacket to remove it + final PacketContainer fakeItemDestroyPacket = ProtocolLibHandler.instance().internal().createPacket(PacketType.Play.Server.ENTITY_DESTROY); + + try { + fakeItemDestroyPacket.getIntLists().write(0, Collections.singletonList(id)); + } catch(final NoSuchMethodError e) { + throw new IllegalStateException("Unable to initialize packet, ProtocolLib update needed", e); + } + return fakeItemDestroyPacket; + } + + /** + * Sends the specified packet to the given player. + * + * @param player the player to receive the packet, cannot be null + * @param packet the packet of type T to be sent, cannot be null + * + * @return true if the packet was successfully sent, false otherwise + */ + @Override + public boolean sendPacket(@NotNull final Player player, @NotNull final PacketContainer packet) { + + ProtocolLibHandler.instance().internal().sendServerPacket(player, packet); + return true; + } + + /** + * Registers the method to listen to the packet sending chunk data. + */ + @Override + public void registerSendChunk() { + + this.chunkSendingPacketAdapter = new PacketAdapter(QuickShop.getInstance().getJavaPlugin(), ListenerPriority.HIGH, PacketType.Play.Server.MAP_CHUNK) { + + @Override + public void onPacketSending(@NotNull final PacketEvent event) { + + final Player player = event.getPlayer(); + if(player == null || !player.isOnline()) { + return; + } + if(player.getClass().getName().contains("TemporaryPlayer")) { + return; + } + final StructureModifier integerStructureModifier = event.getPacket().getIntegers(); + //chunk x + final int x = integerStructureModifier.read(0); + //chunk z + final int z = integerStructureModifier.read(1); + + VirtualDisplayItemManager.instance().getChunksMapping().computeIfPresent(new SimpleShopChunk(player.getWorld().getName(), x, z), (chunkLoc, targetList)->{ + for(final VirtualDisplayItem target : targetList) { + if(!target.isSpawned()) { + continue; + } + if(target.isApplicableForPlayer(player)) { // TODO: Refactor with better way + target.getPacketSenders().add(player.getUniqueId()); + target.sendDestroyPacket(player); + target.sendFakeItem(player); + } + } + return targetList; + }); + } + }; + + ProtocolLibHandler.instance().internal().addPacketListener(chunkSendingPacketAdapter); + } + + /** + * Unregisters the method to listen to the packet sending chunk data. + */ + @Override + public void unregisterSendChunk() { + + if(chunkSendingPacketAdapter != null) { + + ProtocolLibHandler.instance().internal().removePacketListener(chunkSendingPacketAdapter); + } + } + + /** + * Registers the method to listen to the packet sending the unloading of a chunk. + */ + @Override + public void registerUnloadChunk() { + + this.chunkUnloadingPacketAdapter = new PacketAdapter(QuickShop.getInstance().getJavaPlugin(), ListenerPriority.HIGH, PacketType.Play.Server.UNLOAD_CHUNK) { + @Override + public void onPacketSending(@NotNull final PacketEvent event) { + + final Player player = event.getPlayer(); + if(player == null || !player.isOnline()) { + return; + } + if(player.getClass().getName().contains("TemporaryPlayer")) { + return; + } + final StructureModifier intPairStructureModifier = event.getPacket().getChunkCoordIntPairs(); + final ChunkCoordIntPair pair = intPairStructureModifier.read(0); + //chunk x + final int x = pair.getChunkX(); + //chunk z + final int z = pair.getChunkZ(); + VirtualDisplayItemManager.instance().getChunksMapping().computeIfPresent(new SimpleShopChunk(player.getWorld().getName(), x, z), (chunkLoc, targetList)->{ + for(final VirtualDisplayItem target : targetList) { + + if(!target.isSpawned()) { + + continue; + } + target.sendDestroyPacket(player); + target.getPacketSenders().remove(player.getUniqueId()); + } + return targetList; + }); + } + }; + + ProtocolLibHandler.instance().internal().addPacketListener(chunkUnloadingPacketAdapter); + } + + /** + * Unregisters the method to listen to the packet sending the unloading of a chunk. + */ + @Override + public void unregisterUnloadChunk() { + + if(chunkUnloadingPacketAdapter != null) { + + ProtocolLibHandler.instance().internal().removePacketListener(chunkUnloadingPacketAdapter); + } + } +} \ No newline at end of file From 8addc3e6b2928eb3999e6b4d8b145daaeef528f0 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Tue, 23 Dec 2025 21:30:20 -0500 Subject: [PATCH 07/19] Update changelog. --- .changelog/6.2.0.11.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changelog/6.2.0.11.md b/.changelog/6.2.0.11.md index 13964551dc..93fb2e21e5 100644 --- a/.changelog/6.2.0.11.md +++ b/.changelog/6.2.0.11.md @@ -117,4 +117,6 @@ - Fixes critical issue in GriefPrevention compatibility plugin. - Fixes issue with shop owner not getting money when benefits active. - Fixes for lands addon on folia, Use regionThread for Folia compatibility(thanks to YusakiDev) -- Fixes for TRADE_DIRECT on stacking shops \ No newline at end of file +- Fixes for TRADE_DIRECT on stacking shops +- Fixes issue for protocollib 5.4.0 with undocumented packet changes. +- Fixes issue where item stacks could be used on creation above the max stack size. \ No newline at end of file From 87b64ade90ca16450235fe5625644e18763996fc Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Thu, 25 Dec 2025 18:47:48 -0500 Subject: [PATCH 08/19] Style fixes for ternary operators, fix issue with improper text replacements on browse icons. --- .changelog/6.2.0.11.md | 10 + .../quickshop/menu/ShopBrowseMenu.java | 6 +- .../quickshop/menu/ShopHistoryMenu.java | 2 +- .../quickshop/menu/ShopKeeperMenu.java | 2 +- .../quickshop/menu/ShopStaffMenu.java | 2 +- .../quickshop/menu/ShopTradeMenu.java | 2 +- .../menu/browse/BrowseFilterMode.java | 52 +-- .../quickshop/menu/browse/BrowseSortMode.java | 54 +-- .../menu/browse/GroupedItemPage.java | 319 +++++++++--------- .../quickshop/menu/browse/MainPage.java | 41 ++- .../menu/browse/MarketItemGroup.java | 113 ++++--- .../quickshop/menu/browse/MarketUtils.java | 230 +++++++------ .../quickshop/menu/browse/ShopListPage.java | 250 +++++++------- .../quickshop/menu/config/GuiConfig.java | 159 +++++---- .../quickshop/menu/config/GuiIconBuilder.java | 42 ++- .../quickshop/menu/history/MainPage.java | 80 ++--- .../quickshop/menu/keeper/MainPage.java | 160 +++++---- .../menu/shared/ClearSearchAction.java | 7 +- .../quickshop/menu/shared/GuiChatAction.java | 37 +- .../menu/shared/GuiChatInputManager.java | 73 ++-- .../quickshop/menu/shared/QuickShopPage.java | 47 +-- .../menu/staff/PlayerSelectionPage.java | 108 +++--- .../menu/staff/StaffSelectionPage.java | 109 +++--- .../quickshop/menu/trade/MainPage.java | 105 +++--- 24 files changed, 1087 insertions(+), 923 deletions(-) diff --git a/.changelog/6.2.0.11.md b/.changelog/6.2.0.11.md index 93fb2e21e5..31eedc7e26 100644 --- a/.changelog/6.2.0.11.md +++ b/.changelog/6.2.0.11.md @@ -47,6 +47,16 @@ - Wildcard patterns like *_AXE and *_SPAWN_EGG now work in blacklist configuration - Supports * for any characters and ? for single character matching - Case-insensitive pattern matching +- Overhauled GUI system(thanks to Yusakidev) + - Implemented gui.yml configuration file for customizing the GUI. + - Implemented better filtering for qs browse + - Implement GUI system to be more stable, and less error prone. + - Added gui.yml for fully configurable GUI layouts and text + - Browse menu: search, sort (price/name/stock), and filter (all/buying/selling/in-stock) + - Staff menu: search functionality for player/staff selection + - Grouped item pages for better shop browsing + - Refactored all menu pages to use configurable display/lore + - Moved GUI messages from messages.yml to gui.yml ## Addons/Compats Changes - Added UltimateClaims compat diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java index 2ff9179b17..42703811c5 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java @@ -40,7 +40,7 @@ public class ShopBrowseMenu extends Menu { public static final String BROWSE_SEARCH = "BROWSE_SEARCH"; public static final String BROWSE_STOCK_ONLY = "BROWSE_STOCK_ONLY"; public static final String BROWSE_WORLD_ONLY = "BROWSE_WORLD_ONLY"; - + // Data keys for shop list page (when viewing shops for a specific item) public static final String SELECTED_ITEM_SHOPS = "SELECTED_ITEM_SHOPS"; public static final String SHOP_LIST_PAGE = "SHOP_LIST_PAGE"; @@ -49,7 +49,7 @@ public ShopBrowseMenu() { // Load rows from GUI config final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("browse"); - this.rows = (menuConfig != null?menuConfig.getRows() : 6); + this.rows = (menuConfig != null? menuConfig.getRows() : 6); this.name = "qs:browse"; setOpen((open)->open.getMenu().setTitle(QuickShop.getInstance().text().of(open.getPlayer().identifier(), "gui.browse.title").legacy())); @@ -59,7 +59,7 @@ public ShopBrowseMenu() { final GroupedItemPage groupedPageHandler = new GroupedItemPage(this.name, this.rows); groupedPage.setOpen(groupedPageHandler::handle); addPage(groupedPage); - + // Page 2: Shop list view (all shops for a specific item) final Page shopListPage = new Page(2); final ShopListPage shopListPageHandler = new ShopListPage(this.name, this.rows); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java index b4e81c1576..20e6038c42 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java @@ -40,7 +40,7 @@ public ShopHistoryMenu() { // Load rows from GUI config final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("history"); - this.rows = (menuConfig != null?menuConfig.getRows() : 6); + this.rows = (menuConfig != null? menuConfig.getRows() : 6); this.name = "qs:history"; setOpen((open)->open.getMenu().setTitle(QuickShop.getInstance().text().of(open.getPlayer().identifier(), "history.shop.gui-title").legacy())); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java index 4f9888af25..693a82e052 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java @@ -38,7 +38,7 @@ public ShopKeeperMenu() { // Load rows from config or use default (4 rows for modern layout) final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("keeper"); - this.rows = (menuConfig != null?menuConfig.getRows() : 4); + this.rows = (menuConfig != null? menuConfig.getRows() : 4); this.name = "qs:keeper"; setOpen((open)->open.getMenu().setTitle(legacy(open.getPlayer().identifier(), "gui.keeper.title"))); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java index 77f554fe1f..583649c151 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java @@ -47,7 +47,7 @@ public ShopStaffMenu() { // Load rows from config or use default (6 rows for modern layout) final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("staff"); - this.rows = (menuConfig != null?menuConfig.getRows() : 6); + this.rows = (menuConfig != null? menuConfig.getRows() : 6); this.name = "qs:staff"; this.title = "Shop Staff"; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java index ee42eada10..fc095e2268 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java @@ -34,7 +34,7 @@ public ShopTradeMenu() { // Load rows from config or use default (6 rows for modern layout) final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("trade"); - this.rows = (menuConfig != null?menuConfig.getRows() : 6); + this.rows = (menuConfig != null? menuConfig.getRows() : 6); this.name = "qs:trade"; setOpen((open)->open.getMenu().setTitle(legacy(open.getPlayer().identifier(), "gui.trade.title"))); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseFilterMode.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseFilterMode.java index fff77d6bee..d734961de4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseFilterMode.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseFilterMode.java @@ -24,58 +24,66 @@ * @since 6.2.0.8 */ public enum BrowseFilterMode { - + /** * Show all shops */ ALL("all", "gui.browse.filter.all"), - + /** * Show only buying shops (shops that buy from players) */ BUYING("buying", "gui.browse.filter.buying"), - + /** * Show only selling shops (shops that sell to players) */ SELLING("selling", "gui.browse.filter.selling"); - + private final String id; private final String translationKey; - + BrowseFilterMode(final String id, final String translationKey) { + this.id = id; this.translationKey = translationKey; } - + + /** + * Get filter mode from id string + * + * @param id The id string + * + * @return The filter mode, or ALL if not found + */ + public static BrowseFilterMode fromId(final String id) { + + for(final BrowseFilterMode mode : values()) { + if(mode.getId().equalsIgnoreCase(id)) { + return mode; + } + } + return ALL; + } + public String getId() { + return id; } - + public String getTranslationKey() { + return translationKey; } - + /** * Get the next filter mode in the cycle + * * @return The next filter mode */ public BrowseFilterMode next() { + final BrowseFilterMode[] values = values(); return values[(this.ordinal() + 1) % values.length]; } - - /** - * Get filter mode from id string - * @param id The id string - * @return The filter mode, or ALL if not found - */ - public static BrowseFilterMode fromId(final String id) { - for(final BrowseFilterMode mode : values()) { - if(mode.getId().equalsIgnoreCase(id)) { - return mode; - } - } - return ALL; - } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseSortMode.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseSortMode.java index 448843172e..5eb638d4b2 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseSortMode.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/BrowseSortMode.java @@ -24,63 +24,71 @@ * @since 6.2.0.8 */ public enum BrowseSortMode { - + /** * Sort by price, lowest first */ PRICE_ASC("price-low-high", "gui.browse.sort.price-asc"), - + /** * Sort by price, highest first */ PRICE_DESC("price-high-low", "gui.browse.sort.price-desc"), - + /** * Sort by stock amount, highest first */ STOCK("stock", "gui.browse.sort.stock"), - + /** * Sort by item name alphabetically */ NAME("name", "gui.browse.sort.name"); - + private final String id; private final String translationKey; - + BrowseSortMode(final String id, final String translationKey) { + this.id = id; this.translationKey = translationKey; } - + + /** + * Get sort mode from id string + * + * @param id The id string + * + * @return The sort mode, or PRICE_ASC if not found + */ + public static BrowseSortMode fromId(final String id) { + + for(final BrowseSortMode mode : values()) { + if(mode.getId().equalsIgnoreCase(id)) { + return mode; + } + } + return PRICE_ASC; + } + public String getId() { + return id; } - + public String getTranslationKey() { + return translationKey; } - + /** * Get the next sort mode in the cycle + * * @return The next sort mode */ public BrowseSortMode next() { + final BrowseSortMode[] values = values(); return values[(this.ordinal() + 1) % values.length]; } - - /** - * Get sort mode from id string - * @param id The id string - * @return The sort mode, or PRICE_ASC if not found - */ - public static BrowseSortMode fromId(final String id) { - for(final BrowseSortMode mode : values()) { - if(mode.getId().equalsIgnoreCase(id)) { - return mode; - } - } - return PRICE_ASC; - } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java index be14a09abb..a2d2704c29 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java @@ -23,7 +23,6 @@ import com.ghostchu.quickshop.menu.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.ClearSearchAction; import com.ghostchu.quickshop.menu.shared.GuiChatAction; -import net.tnemc.menu.core.icon.action.ActionType; import net.kyori.adventure.text.Component; import net.tnemc.item.AbstractItemStack; import net.tnemc.item.bukkit.BukkitItemStack; @@ -31,6 +30,7 @@ import net.tnemc.menu.core.builder.IconBuilder; import net.tnemc.menu.core.callbacks.page.PageOpenCallback; import net.tnemc.menu.core.compatibility.MenuPlayer; +import net.tnemc.menu.core.icon.action.ActionType; import net.tnemc.menu.core.icon.action.impl.DataAction; import net.tnemc.menu.core.icon.action.impl.RunnableAction; import net.tnemc.menu.core.icon.action.impl.SwitchPageAction; @@ -59,8 +59,8 @@ import static com.ghostchu.quickshop.menu.shared.QuickShopPage.guiMessage; /** - * GroupedItemPage - Market overview page showing items grouped by type - * with price statistics and the ability to drill down into specific items + * GroupedItemPage - Market overview page showing items grouped by type with price statistics and + * the ability to drill down into specific items * * @author creatorfromhell * @since 6.2.0.8 @@ -71,48 +71,49 @@ public class GroupedItemPage { private final int menuRows; public GroupedItemPage(final String menuName, final int menuRows) { + this.menuName = menuName; this.menuRows = Math.max(menuRows, 3); // Minimum 3 rows } public void handle(final PageOpenCallback callback) { + final Optional viewerOpt = callback.getPlayer().viewer(); if(viewerOpt.isEmpty()) return; - + final MenuViewer viewer = viewerOpt.get(); final Page menuPage = callback.getPage(); final Optional shopsData = viewer.findData(SHOPS_DATA); final UUID id = viewer.uuid(); final Player player = Bukkit.getPlayer(id); - + if(shopsData.isEmpty() || player == null) return; menuPage.getIcons().clear(); // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("browse"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig searchConfig = menuConfig != null?menuConfig.getIcon("search") : null; - final GuiConfig.IconConfig sortConfig = menuConfig != null?menuConfig.getIcon("sort") : null; - final GuiConfig.IconConfig filterConfig = menuConfig != null?menuConfig.getIcon("filter") : null; - final GuiConfig.IconConfig stockConfig = menuConfig != null?menuConfig.getIcon("stock-filter") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig pageInfoConfig = menuConfig != null?menuConfig.getIcon("page-info") : null; - final GuiConfig.IconConfig closeConfig = menuConfig != null?menuConfig.getIcon("close") : null; - - final int listStartSlot = menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9; + final GuiConfig.IconConfig borderConfig = (menuConfig != null)? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig searchConfig = (menuConfig != null)? menuConfig.getIcon("search") : null; + final GuiConfig.IconConfig sortConfig = (menuConfig != null)? menuConfig.getIcon("sort") : null; + final GuiConfig.IconConfig filterConfig = (menuConfig != null)? menuConfig.getIcon("filter") : null; + final GuiConfig.IconConfig stockConfig = (menuConfig != null)? menuConfig.getIcon("stock-filter") : null; + final GuiConfig.IconConfig prevPageConfig = (menuConfig != null)? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = (menuConfig != null)? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig pageInfoConfig = (menuConfig != null)? menuConfig.getIcon("page-info") : null; + final GuiConfig.IconConfig closeConfig = (menuConfig != null)? menuConfig.getIcon("close") : null; + + final int listStartSlot = (menuConfig != null)? menuConfig.getSection().getInt("list-start-slot", 9) : 9; // Get current state - final BrowseSortMode sortMode = (BrowseSortMode) viewer.dataOrDefault(BROWSE_SORT, BrowseSortMode.PRICE_ASC); - final BrowseFilterMode filterMode = (BrowseFilterMode) viewer.dataOrDefault(BROWSE_FILTER, BrowseFilterMode.ALL); - final String searchQuery = (String) viewer.dataOrDefault(BROWSE_SEARCH, ""); - final boolean stockOnly = (Boolean) viewer.dataOrDefault(BROWSE_STOCK_ONLY, false); - final int page = (Integer) viewer.dataOrDefault(SHOPS_PAGE, 1); + final BrowseSortMode sortMode = (BrowseSortMode)viewer.dataOrDefault(BROWSE_SORT, BrowseSortMode.PRICE_ASC); + final BrowseFilterMode filterMode = (BrowseFilterMode)viewer.dataOrDefault(BROWSE_FILTER, BrowseFilterMode.ALL); + final String searchQuery = (String)viewer.dataOrDefault(BROWSE_SEARCH, ""); + final boolean stockOnly = (Boolean)viewer.dataOrDefault(BROWSE_STOCK_ONLY, false); + final int page = (Integer)viewer.dataOrDefault(SHOPS_PAGE, 1); - @SuppressWarnings("unchecked") - final List allShops = (ArrayList) shopsData.get(); + @SuppressWarnings("unchecked") final List allShops = (ArrayList)shopsData.get(); // Process shops into groups with current filters/sort/search final List groups = MarketUtils.processGroups(allShops, filterMode, sortMode, searchQuery, stockOnly); @@ -121,141 +122,141 @@ public void handle(final PageOpenCallback callback) { final int offset = 9; final int items = (menuRows - 2) * offset; final int start = ((page - 1) * offset); - final int maxPages = (groups.size() / items) + (((groups.size() % items) > 0)?1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int maxPages = (groups.size() / items) + (((groups.size() % items) > 0)? 1 : 0); + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // Set up border rows - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = (borderConfig != null)? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); + final List borderRows = (borderConfig != null)? borderConfig.getRows() : List.of(1, 6); for(final int row : borderRows) { menuPage.setRow(row, borderBuilder); } // === Control Row (Row 1) === - + // Search button (slot 0) - Left-click to search, Right-click to clear - final String searchMaterial = searchConfig != null?searchConfig.getMaterial() : "ANVIL"; - final int searchSlot = searchConfig != null?searchConfig.getSlot() : 0; - final String currentSearchDisplay = searchQuery.isEmpty()?"None" : searchQuery; - + final String searchMaterial = (searchConfig != null)? searchConfig.getMaterial() : "ANVIL"; + final int searchSlot = (searchConfig != null)? searchConfig.getSlot() : 0; + final String currentSearchDisplay = searchQuery.isEmpty()? "None" : searchQuery; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(searchMaterial, 1) - .display(getConfigDisplay(searchConfig, "Search: {0}", currentSearchDisplay)) - .lore(getConfigLore(searchConfig, currentSearchDisplay))) - .withSlot(searchSlot) - .withActions(new GuiChatAction((message) -> { - // Handle clear command - final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))?"" : message; - - // Create new viewer with ALL state preserved + new search value - // (TNMS removes viewer when inventory closes, so we recreate it) - final MenuViewer newViewer = new MenuViewer(id); - newViewer.addData(SHOPS_DATA, allShops); // Captured from closure - newViewer.addData(BROWSE_SORT, sortMode); // Captured from closure - newViewer.addData(BROWSE_FILTER, filterMode); // Captured from closure - newViewer.addData(BROWSE_STOCK_ONLY, stockOnly); // Captured from closure - newViewer.addData(BROWSE_SEARCH, searchValue); // New search value - newViewer.addData(SHOPS_PAGE, 1); // Reset to page 1 on new search - MenuManager.instance().addViewer(newViewer); - - // Manually reopen the menu - final Player p = Bukkit.getPlayer(id); - if(p != null && p.isOnline()) { - final MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(p); - menuPlayer.inventory().openMenu(menuPlayer, menuName, 1); // Page 1 is GroupedItemPage - } - return true; - }, guiMessage("browse.enter-search"), false, ActionType.LEFT_CLICK)) // Left-click for search input - .withActions(new ClearSearchAction(BROWSE_SEARCH, SHOPS_PAGE, menuName, 1)) // Right-click to clear - .build()); + .display(getConfigDisplay(searchConfig, "Search: {0}", currentSearchDisplay)) + .lore(getConfigLore(searchConfig, currentSearchDisplay))) + .withSlot(searchSlot) + .withActions(new GuiChatAction((message)->{ + // Handle clear command + final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))? "" : message; + + // Create new viewer with ALL state preserved + new search value + // (TNMS removes viewer when inventory closes, so we recreate it) + final MenuViewer newViewer = new MenuViewer(id); + newViewer.addData(SHOPS_DATA, allShops); // Captured from closure + newViewer.addData(BROWSE_SORT, sortMode); // Captured from closure + newViewer.addData(BROWSE_FILTER, filterMode); // Captured from closure + newViewer.addData(BROWSE_STOCK_ONLY, stockOnly); // Captured from closure + newViewer.addData(BROWSE_SEARCH, searchValue); // New search value + newViewer.addData(SHOPS_PAGE, 1); // Reset to page 1 on new search + MenuManager.instance().addViewer(newViewer); + + // Manually reopen the menu + final Player p = Bukkit.getPlayer(id); + if(p != null && p.isOnline()) { + final MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(p); + menuPlayer.inventory().openMenu(menuPlayer, menuName, 1); // Page 1 is GroupedItemPage + } + return true; + }, guiMessage("browse.enter-search"), false, ActionType.LEFT_CLICK)) // Left-click for search input + .withActions(new ClearSearchAction(BROWSE_SEARCH, SHOPS_PAGE, menuName, 1)) // Right-click to clear + .build()); // Sort button (slot 2) - final String sortMaterial = sortConfig != null?sortConfig.getMaterial() : "HOPPER"; - final int sortSlot = sortConfig != null?sortConfig.getSlot() : 2; - + final String sortMaterial = (sortConfig != null)? sortConfig.getMaterial() : "HOPPER"; + final int sortSlot = (sortConfig != null)? sortConfig.getSlot() : 2; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(sortMaterial, 1) - .display(getConfigDisplay(sortConfig, "Sort: {0}", getSortDisplayName(sortMode))) - .lore(getConfigLore(sortConfig))) - .withSlot(sortSlot) - .withActions( - new DataAction(BROWSE_SORT, sortMode.next()), - new DataAction(SHOPS_PAGE, 1), - new SwitchPageAction(menuName, 1) - ) - .build()); + .display(getConfigDisplay(sortConfig, "Sort: {0}", getSortDisplayName(sortMode))) + .lore(getConfigLore(sortConfig))) + .withSlot(sortSlot) + .withActions( + new DataAction(BROWSE_SORT, sortMode.next()), + new DataAction(SHOPS_PAGE, 1), + new SwitchPageAction(menuName, 1) + ) + .build()); // Filter button (slot 4) - final String filterMaterial = filterConfig != null?filterConfig.getMaterial() : "NAME_TAG"; - final int filterSlot = filterConfig != null?filterConfig.getSlot() : 4; - + final String filterMaterial = (filterConfig != null)? filterConfig.getMaterial() : "NAME_TAG"; + final int filterSlot = (filterConfig != null)? filterConfig.getSlot() : 4; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(filterMaterial, 1) - .display(getConfigDisplay(filterConfig, "Filter: {0}", getFilterDisplayName(filterMode))) - .lore(getConfigLore(filterConfig))) - .withSlot(filterSlot) - .withActions( - new DataAction(BROWSE_FILTER, filterMode.next()), - new DataAction(SHOPS_PAGE, 1), - new SwitchPageAction(menuName, 1) - ) - .build()); + .display(getConfigDisplay(filterConfig, "Filter: {0}", getFilterDisplayName(filterMode))) + .lore(getConfigLore(filterConfig))) + .withSlot(filterSlot) + .withActions( + new DataAction(BROWSE_FILTER, filterMode.next()), + new DataAction(SHOPS_PAGE, 1), + new SwitchPageAction(menuName, 1) + ) + .build()); // Stock filter toggle button (slot 6) - final String stockMaterial = stockConfig != null?stockConfig.getMaterial() : "CHEST"; - final int stockSlot = stockConfig != null?stockConfig.getSlot() : 6; - final String stockStatus = stockOnly?"ON" : "OFF"; - + final String stockMaterial = (stockConfig != null)? stockConfig.getMaterial() : "CHEST"; + final int stockSlot = (stockConfig != null)? stockConfig.getSlot() : 6; + final String stockStatus = (stockOnly)? "ON" : "OFF"; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(stockMaterial, 1) - .display(getConfigDisplay(stockConfig, "In Stock Only: {0}", stockStatus)) - .lore(getConfigLore(stockConfig))) - .withSlot(stockSlot) - .withActions( - new DataAction(BROWSE_STOCK_ONLY, !stockOnly), - new DataAction(SHOPS_PAGE, 1), - new SwitchPageAction(menuName, 1) - ) - .build()); + .display(getConfigDisplay(stockConfig, "In Stock Only: {0}", stockStatus)) + .lore(getConfigLore(stockConfig))) + .withSlot(stockSlot) + .withActions( + new DataAction(BROWSE_STOCK_ONLY, !stockOnly), + new DataAction(SHOPS_PAGE, 1), + new SwitchPageAction(menuName, 1) + ) + .build()); // Close button (slot 8) - final String closeMaterial = closeConfig != null?closeConfig.getMaterial() : "BARRIER"; - final int closeSlot = closeConfig != null?closeConfig.getSlot() : 8; - + final String closeMaterial = (closeConfig != null)? closeConfig.getMaterial() : "BARRIER"; + final int closeSlot = (closeConfig != null)? closeConfig.getSlot() : 8; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(closeMaterial, 1) - .display(getConfigDisplay(closeConfig, "Close"))) - .withSlot(closeSlot) - .withActions(new RunnableAction((click) -> { - final Player p = Bukkit.getPlayer(click.player().identifier()); - if(p != null) p.closeInventory(); - })) - .build()); - - // === Pagination Row (Bottom) === - final String prevMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevSlot = prevPageConfig != null?prevPageConfig.getSlot() : 48; - final String nextMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextSlot = nextPageConfig != null?nextPageConfig.getSlot() : 50; - final String pageInfoMaterial = pageInfoConfig != null?pageInfoConfig.getMaterial() : "BOOK"; - final int pageInfoSlot = pageInfoConfig != null?pageInfoConfig.getSlot() : 49; - + .display(getConfigDisplay(closeConfig, "Close"))) + .withSlot(closeSlot) + .withActions(new RunnableAction((click)->{ + final Player p = Bukkit.getPlayer(click.player().identifier()); + if(p != null) p.closeInventory(); + })) + .build()); + + // Pagination Row (Bottom) + final String prevMaterial = (prevPageConfig != null)? prevPageConfig.getMaterial() : "ARROW"; + final int prevSlot = (prevPageConfig != null)? prevPageConfig.getSlot() : 48; + final String nextMaterial = (nextPageConfig != null)? nextPageConfig.getMaterial() : "ARROW"; + final int nextSlot = (nextPageConfig != null)? nextPageConfig.getSlot() : 50; + final String pageInfoMaterial = (pageInfoConfig != null)? pageInfoConfig.getMaterial() : "BOOK"; + final int pageInfoSlot = (pageInfoConfig != null)? pageInfoConfig.getSlot() : 49; + if(maxPages > 1) { menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(prevMaterial, 1) - .display(getConfigDisplay(prevPageConfig, "<< Previous Page"))) - .withSlot(prevSlot) - .withActions(new DataAction(SHOPS_PAGE, prev), new SwitchPageAction(menuName, 1)) - .build()); + .display(getConfigDisplay(prevPageConfig, "<< Previous Page"))) + .withSlot(prevSlot) + .withActions(new DataAction(SHOPS_PAGE, prev), new SwitchPageAction(menuName, 1)) + .build()); menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(nextMaterial, 1) - .display(getConfigDisplay(nextPageConfig, "Next Page >>"))) - .withSlot(nextSlot) - .withActions(new DataAction(SHOPS_PAGE, next), new SwitchPageAction(menuName, 1)) - .build()); + .display(getConfigDisplay(nextPageConfig, "Next Page >>"))) + .withSlot(nextSlot) + .withActions(new DataAction(SHOPS_PAGE, next), new SwitchPageAction(menuName, 1)) + .build()); } // Page info menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(pageInfoMaterial, 1) - .display(getConfigDisplay(pageInfoConfig, "Page {0}/{1}", page, Math.max(1, maxPages)))) - .withSlot(pageInfoSlot) - .build()); + .display(getConfigDisplay(pageInfoConfig, "Page {0}/{1}", page, Math.max(1, maxPages)))) + .withSlot(pageInfoSlot) + .build()); // === Item Grid === int i = 0; @@ -265,13 +266,13 @@ public void handle(final PageOpenCallback callback) { continue; } if(i >= (start + items)) break; - + // Build item lore with market statistics final List lore = buildGroupLore(id, group); - + // Get display name for the item final String itemName = CommonUtil.prettifyText(group.getRepresentativeItem().getType().name()); - + final AbstractItemStack stack = new BukkitItemStack() .of(group.getRepresentativeItem().getType().key().asString(), 1) .display(QuickShop.getInstance().platform().miniMessage().deserialize("" + itemName + "")) @@ -280,28 +281,29 @@ public void handle(final PageOpenCallback callback) { // Find ALL shops for this item type from unfiltered list (so filter can be changed on ShopListPage) final ItemStack representativeItem = group.getRepresentativeItem(); final List allShopsForItem = allShops.stream() - .filter(s -> s.getItem().getType() == representativeItem.getType()) + .filter(s->s.getItem().getType() == representativeItem.getType()) .toList(); - + menuPage.addIcon(new IconBuilder(stack) - .withSlot(listStartSlot + (i - start)) - .withActions( - new RunnableAction((click) -> { - final Optional v = click.player().viewer(); - if(v.isPresent()) { - v.get().addData(SELECTED_ITEM_SHOPS, new ArrayList<>(allShopsForItem)); - v.get().addData(SHOP_LIST_PAGE, 1); - } - }), - new SwitchPageAction(menuName, 2) - ) - .build()); - + .withSlot(listStartSlot + (i - start)) + .withActions( + new RunnableAction((click)->{ + final Optional v = click.player().viewer(); + if(v.isPresent()) { + v.get().addData(SELECTED_ITEM_SHOPS, new ArrayList<>(allShopsForItem)); + v.get().addData(SHOP_LIST_PAGE, 1); + } + }), + new SwitchPageAction(menuName, 2) + ) + .build()); + i++; } } - + private String getSortDisplayName(final BrowseSortMode mode) { + return switch(mode) { case PRICE_ASC -> "Price ↑"; case PRICE_DESC -> "Price ↓"; @@ -309,8 +311,9 @@ private String getSortDisplayName(final BrowseSortMode mode) { case NAME -> "Name"; }; } - + private String getFilterDisplayName(final BrowseFilterMode mode) { + return switch(mode) { case ALL -> "All"; case BUYING -> "Buying"; @@ -322,36 +325,37 @@ private String getFilterDisplayName(final BrowseFilterMode mode) { * Build the lore for a grouped item showing market statistics */ private List buildGroupLore(final UUID playerId, final MarketItemGroup group) { + final List lore = new ArrayList<>(); final var mm = QuickShop.getInstance().platform().miniMessage(); - + // Total shops count lore.add(mm.deserialize("Shops: " + group.getTotalShopCount() + "")); lore.add(Component.empty()); - + // Selling shops statistics if(group.hasSellingShops()) { lore.add(mm.deserialize("▼ Selling (" + group.getSellingShopCount() + " shops)")); - lore.add(mm.deserialize(" Price: " + formatPrice(group.getSellingMinPrice()) + - " - " + formatPrice(group.getSellingMaxPrice()) + "")); + lore.add(mm.deserialize(" Price: " + formatPrice(group.getSellingMinPrice()) + + " - " + formatPrice(group.getSellingMaxPrice()) + "")); lore.add(mm.deserialize(" Average: " + formatPrice(group.getSellingAvgPrice()) + "")); lore.add(mm.deserialize(" Median: " + formatPrice(group.getSellingMedianPrice()) + "")); lore.add(mm.deserialize(" Stock: " + group.getSellingTotalStock() + "")); } - + // Buying shops statistics if(group.hasBuyingShops()) { if(group.hasSellingShops()) lore.add(Component.empty()); lore.add(mm.deserialize("<#FFA500>▲ Buying (" + group.getBuyingShopCount() + " shops)")); - lore.add(mm.deserialize(" Price: " + formatPrice(group.getBuyingMinPrice()) + - " - " + formatPrice(group.getBuyingMaxPrice()) + "")); + lore.add(mm.deserialize(" Price: " + formatPrice(group.getBuyingMinPrice()) + + " - " + formatPrice(group.getBuyingMaxPrice()) + "")); lore.add(mm.deserialize(" Average: " + formatPrice(group.getBuyingAvgPrice()) + "")); lore.add(mm.deserialize(" Median: " + formatPrice(group.getBuyingMedianPrice()) + "")); } - + lore.add(Component.empty()); lore.add(mm.deserialize("Click to view all shops")); - + return lore; } @@ -359,6 +363,7 @@ private List buildGroupLore(final UUID playerId, final MarketItemGrou * Format a price value */ private String formatPrice(final double price) { + return QuickShop.getInstance().getEconomyManager().provider() .format(BigDecimal.valueOf(price), null, null); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java index 70a0785011..50548ec551 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java @@ -47,7 +47,6 @@ import static com.ghostchu.quickshop.menu.shared.QuickShopPage.get; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigDisplay; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigLore; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getList; /** * MainPage @@ -79,7 +78,7 @@ public MainPage(final String returnMenu, final String menuName, this.actions = actions; //we need a controller row and then at least one row for items. - this.menuRows = (menuRows <= 1)?3 : menuRows; + this.menuRows = (menuRows <= 1)? 3 : menuRows; } public void handle(final PageOpenCallback callback) { @@ -96,12 +95,12 @@ public void handle(final PageOpenCallback callback) { // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("browse"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig pageInfoConfig = menuConfig != null?menuConfig.getIcon("page-info") : null; + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig prevPageConfig = menuConfig != null? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = menuConfig != null? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig pageInfoConfig = menuConfig != null? menuConfig.getIcon("page-info") : null; - final int listStartSlot = (menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9); + final int listStartSlot = (menuConfig != null? menuConfig.getSection().getInt("list-start-slot", 9) : 9); final int offset = 9; final int page = (Integer)viewer.get().dataOrDefault(staffPageID, 1); @@ -110,26 +109,26 @@ public void handle(final PageOpenCallback callback) { final List shops = (ArrayList)shopsData.get(); - final int maxPages = (shops.size() / items) + (((shops.size() % items) > 0)?1 : 0); + final int maxPages = (shops.size() / items) + (((shops.size() % items) > 0)? 1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // Set up borders from config - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(1, 6); for(final int row : borderRows) { playerPage.setRow(id, row, borderBuilder); } // Pagination icons from config - final String prevPageMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevPageSlot = (prevPageConfig != null?prevPageConfig.getSlot() : 3); - final String nextPageMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextPageSlot = (nextPageConfig != null?nextPageConfig.getSlot() : 5); - final String pageInfoMaterial = pageInfoConfig != null?pageInfoConfig.getMaterial() : "BOOK"; - final int pageInfoSlot = (pageInfoConfig != null?pageInfoConfig.getSlot() : 4); + final String prevPageMaterial = prevPageConfig != null? prevPageConfig.getMaterial() : "ARROW"; + final int prevPageSlot = (prevPageConfig != null? prevPageConfig.getSlot() : 3); + final String nextPageMaterial = nextPageConfig != null? nextPageConfig.getMaterial() : "ARROW"; + final int nextPageSlot = (nextPageConfig != null? nextPageConfig.getSlot() : 5); + final String pageInfoMaterial = pageInfoConfig != null? pageInfoConfig.getMaterial() : "BOOK"; + final int pageInfoSlot = (pageInfoConfig != null? pageInfoConfig.getSlot() : 4); if(maxPages > 1) { @@ -167,7 +166,7 @@ public void handle(final PageOpenCallback callback) { if(i >= (start + items)) break; - final String world = (shop.getLocation().getWorld() != null)?shop.getLocation().getWorld().getName() : "World"; + final String world = (shop.getLocation().getWorld() != null)? shop.getLocation().getWorld().getName() : "World"; final String location = world + " " + shop.getLocation().getBlockX() + ", " + shop.getLocation().getBlockY() + ", " + shop.getLocation().getBlockZ(); final QUser owner = shop.getOwner(); SkullProfile ownerProfile = null; @@ -179,7 +178,7 @@ public void handle(final PageOpenCallback callback) { final EconomyProvider eco = QuickShop.getInstance().getEconomyManager().provider(); final String priceFormatted = eco.format(BigDecimal.valueOf(shop.getPrice()), shop.getLocation().getWorld().getName(), shop.getCurrency()); - + // Build lore dynamically for shop info final List shopLore = new ArrayList<>(); shopLore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Owner: " + shop.getOwner().getDisplay() + "")); @@ -187,7 +186,7 @@ public void handle(final PageOpenCallback callback) { shopLore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Type: " + shop.shopType().identifier() + "")); shopLore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Price: " + priceFormatted + "")); shopLore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Stock: " + MarketUtils.getStockFromCache(shop) + "")); - + final AbstractItemStack stack = new BukkitItemStack().of(shop.getItem().getType().key().asString(), shop.getShopStackingAmount()) .lore(shopLore); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketItemGroup.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketItemGroup.java index 1f10b67128..1f22ce4d3d 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketItemGroup.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketItemGroup.java @@ -26,45 +26,48 @@ import java.util.List; /** - * MarketItemGroup - Represents a group of shops selling/buying the same item - * with aggregated price statistics for market overview + * MarketItemGroup - Represents a group of shops selling/buying the same item with aggregated price + * statistics for market overview * * @author creatorfromhell * @since 6.2.0.8 */ public class MarketItemGroup { - + private final ItemStack representativeItem; private final List shops; private final List sellingShops; private final List buyingShops; - + // Price statistics for selling shops private double sellingMinPrice; private double sellingMaxPrice; private double sellingAvgPrice; private double sellingMedianPrice; private int sellingTotalStock; - + // Price statistics for buying shops private double buyingMinPrice; private double buyingMaxPrice; private double buyingAvgPrice; private double buyingMedianPrice; private int buyingTotalSpace; - + public MarketItemGroup(@NotNull final ItemStack representativeItem) { + this.representativeItem = representativeItem.clone(); this.shops = new ArrayList<>(); this.sellingShops = new ArrayList<>(); this.buyingShops = new ArrayList<>(); } - + /** * Add a shop to this group + * * @param shop The shop to add */ public void addShop(@NotNull final Shop shop) { + shops.add(shop); if(shop.isSelling()) { sellingShops.add(shop); @@ -72,10 +75,10 @@ public void addShop(@NotNull final Shop shop) { buyingShops.add(shop); } } - + /** - * Calculate price statistics for all shops in this group - * Should be called after all shops have been added + * Calculate price statistics for all shops in this group Should be called after all shops have + * been added */ public void calculateStatistics() { // Calculate selling shop statistics @@ -83,42 +86,45 @@ public void calculateStatistics() { final List sellingPrices = sellingShops.stream() .map(Shop::getPrice) .toList(); - + sellingMinPrice = CommonUtil.min(sellingPrices); sellingMaxPrice = CommonUtil.max(sellingPrices); sellingAvgPrice = CommonUtil.avg(sellingPrices); sellingMedianPrice = CommonUtil.med(sellingPrices); sellingTotalStock = sellingShops.stream() - .mapToInt(shop -> Math.max(0, MarketUtils.getStockFromCache(shop))) + .mapToInt(shop->Math.max(0, MarketUtils.getStockFromCache(shop))) .sum(); } - + // Calculate buying shop statistics if(!buyingShops.isEmpty()) { final List buyingPrices = buyingShops.stream() .map(Shop::getPrice) .toList(); - + buyingMinPrice = CommonUtil.min(buyingPrices); buyingMaxPrice = CommonUtil.max(buyingPrices); buyingAvgPrice = CommonUtil.avg(buyingPrices); buyingMedianPrice = CommonUtil.med(buyingPrices); buyingTotalSpace = buyingShops.stream() - .mapToInt(shop -> Math.max(0, MarketUtils.getSpaceFromCache(shop))) + .mapToInt(shop->Math.max(0, MarketUtils.getSpaceFromCache(shop))) .sum(); } } - + /** * Get a price indicator string based on where a price falls relative to the average - * @param price The price to check + * + * @param price The price to check * @param isSelling Whether this is for a selling shop + * * @return Color indicator: "low" (below avg), "avg" (around avg), "high" (above avg) */ public String getPriceIndicator(final double price, final boolean isSelling) { - final double avg = isSelling?sellingAvgPrice : buyingAvgPrice; + + final double avg = isSelling? sellingAvgPrice : buyingAvgPrice; if(avg == 0) return "avg"; - + final double ratio = price / avg; if(ratio < 0.9) { return "low"; @@ -127,107 +133,130 @@ public String getPriceIndicator(final double price, final boolean isSelling) { } return "avg"; } - + // Getters - + @NotNull public ItemStack getRepresentativeItem() { + return representativeItem.clone(); } - + @NotNull public List getShops() { + return new ArrayList<>(shops); } - + @NotNull public List getSellingShops() { + return new ArrayList<>(sellingShops); } - + @NotNull public List getBuyingShops() { + return new ArrayList<>(buyingShops); } - + public int getTotalShopCount() { + return shops.size(); } - + public int getSellingShopCount() { + return sellingShops.size(); } - + public int getBuyingShopCount() { + return buyingShops.size(); } - + // Selling statistics - + public double getSellingMinPrice() { + return sellingMinPrice; } - + public double getSellingMaxPrice() { + return sellingMaxPrice; } - + public double getSellingAvgPrice() { + return sellingAvgPrice; } - + public double getSellingMedianPrice() { + return sellingMedianPrice; } - + public int getSellingTotalStock() { + return sellingTotalStock; } - + // Buying statistics - + public double getBuyingMinPrice() { + return buyingMinPrice; } - + public double getBuyingMaxPrice() { + return buyingMaxPrice; } - + public double getBuyingAvgPrice() { + return buyingAvgPrice; } - + public double getBuyingMedianPrice() { + return buyingMedianPrice; } - + public int getBuyingTotalSpace() { + return buyingTotalSpace; } - + /** * Check if this group has any selling shops + * * @return true if there are selling shops */ public boolean hasSellingShops() { + return !sellingShops.isEmpty(); } - + /** * Check if this group has any buying shops + * * @return true if there are buying shops */ public boolean hasBuyingShops() { + return !buyingShops.isEmpty(); } - + /** * Get the item display name for this group + * * @return The item's display name or material name */ @NotNull public String getItemDisplayName() { + if(representativeItem.hasItemMeta() && representativeItem.getItemMeta().hasDisplayName()) { return representativeItem.getItemMeta().getDisplayName(); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketUtils.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketUtils.java index d33b207bf5..7c80ca35e3 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketUtils.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MarketUtils.java @@ -32,31 +32,34 @@ import java.util.Locale; /** - * MarketUtils - Utility class for market/browse operations - * Handles grouping shops by item, filtering, sorting, and searching + * MarketUtils - Utility class for market/browse operations Handles grouping shops by item, + * filtering, sorting, and searching * * @author creatorfromhell * @since 6.2.0.8 */ public final class MarketUtils { - + private MarketUtils() { // Utility class } - + /** * Group shops by item type using the ItemMatcher + * * @param shops List of shops to group + * * @return List of MarketItemGroups */ @NotNull public static List groupShopsByItem(@NotNull final List shops) { + final List groups = new ArrayList<>(); final ItemMatcher matcher = QuickShop.getInstance().getItemMatcher(); - + for(final Shop shop : shops) { MarketItemGroup matchingGroup = null; - + // Find existing group that matches this shop's item for(final MarketItemGroup group : groups) { if(matcher.matches(group.getRepresentativeItem(), shop.getItem())) { @@ -64,33 +67,36 @@ public static List groupShopsByItem(@NotNull final List s break; } } - + // Create new group if no match found if(matchingGroup == null) { matchingGroup = new MarketItemGroup(shop.getItem()); groups.add(matchingGroup); } - + matchingGroup.addShop(shop); } - + // Calculate statistics for all groups for(final MarketItemGroup group : groups) { group.calculateStatistics(); } - + return groups; } - + /** * Filter shops based on filter mode - * @param shops List of shops to filter + * + * @param shops List of shops to filter * @param filterMode The filter mode to apply + * * @return Filtered list of shops */ @NotNull - public static List filterShops(@NotNull final List shops, - @NotNull final BrowseFilterMode filterMode) { + public static List filterShops(@NotNull final List shops, + @NotNull final BrowseFilterMode filterMode) { + return switch(filterMode) { case ALL -> new ArrayList<>(shops); case BUYING -> shops.stream() @@ -101,22 +107,24 @@ public static List filterShops(@NotNull final List shops, .toList(); }; } - + /** - * Filter shops to only show those with stock/space available. - * Uses database cache to avoid Folia cross-region block access issues. - * - * @param shops List of shops to filter + * Filter shops to only show those with stock/space available. Uses database cache to avoid Folia + * cross-region block access issues. + * + * @param shops List of shops to filter * @param stockOnly Whether to filter to stock only + * * @return Filtered list of shops */ @NotNull public static List filterByStock(@NotNull final List shops, final boolean stockOnly) { + if(!stockOnly) { return new ArrayList<>(shops); } return shops.stream() - .filter(shop -> { + .filter(shop->{ if(shop.isUnlimited()) return true; // For selling shops, check stock; for buying shops, check space // Use database cache to avoid Folia cross-region block access @@ -128,16 +136,19 @@ public static List filterByStock(@NotNull final List shops, final bo }) .toList(); } - + /** * Filter item groups based on filter mode - * @param groups List of groups to filter + * + * @param groups List of groups to filter * @param filterMode The filter mode to apply + * * @return Filtered list of groups */ @NotNull public static List filterGroups(@NotNull final List groups, - @NotNull final BrowseFilterMode filterMode) { + @NotNull final BrowseFilterMode filterMode) { + return switch(filterMode) { case ALL -> new ArrayList<>(groups); case BUYING -> groups.stream() @@ -148,26 +159,28 @@ public static List filterGroups(@NotNull final List filterGroupsByStock(@NotNull final List groups, - final boolean stockOnly) { + public static List filterGroupsByStock(@NotNull final List groups, + final boolean stockOnly) { + if(!stockOnly) { return new ArrayList<>(groups); } return groups.stream() - .filter(group -> { + .filter(group->{ // Check if any shop in the group has stock/space // Use database cache to avoid Folia cross-region block access - return group.getShops().stream().anyMatch(shop -> { + return group.getShops().stream().anyMatch(shop->{ if(shop.isUnlimited()) return true; if(shop.isSelling()) { return getStockFromCache(shop) > 0; @@ -178,107 +191,121 @@ public static List filterGroupsByStock(@NotNull final List sortShops(@NotNull final List shops, - @NotNull final BrowseSortMode sortMode) { + @NotNull final BrowseSortMode sortMode) { + final List sorted = new ArrayList<>(shops); - + switch(sortMode) { case PRICE_ASC -> sorted.sort(Comparator.comparingDouble(Shop::getPrice)); case PRICE_DESC -> sorted.sort(Comparator.comparingDouble(Shop::getPrice).reversed()); case STOCK -> sorted.sort(Comparator.comparingInt(MarketUtils::getStockFromCache).reversed()); - case NAME -> sorted.sort(Comparator.comparing(shop -> - CommonUtil.prettifyText(shop.getItem().getType().name()))); + case NAME -> sorted.sort(Comparator.comparing(shop-> + CommonUtil.prettifyText(shop.getItem().getType().name()))); } - + return sorted; } - + /** * Sort item groups based on sort mode - * @param groups List of groups to sort + * + * @param groups List of groups to sort * @param sortMode The sort mode to apply + * * @return Sorted list of groups */ @NotNull public static List sortGroups(@NotNull final List groups, - @NotNull final BrowseSortMode sortMode) { + @NotNull final BrowseSortMode sortMode) { + final List sorted = new ArrayList<>(groups); - + switch(sortMode) { - case PRICE_ASC -> sorted.sort(Comparator.comparingDouble(group -> { + case PRICE_ASC -> sorted.sort(Comparator.comparingDouble(group->{ // Use selling price if available, otherwise buying price if(group.hasSellingShops()) { return group.getSellingMinPrice(); } return group.getBuyingMinPrice(); })); - case PRICE_DESC -> sorted.sort(Comparator.comparingDouble((MarketItemGroup group) -> { + case PRICE_DESC -> sorted.sort(Comparator.comparingDouble((final MarketItemGroup group)->{ if(group.hasSellingShops()) { return group.getSellingMaxPrice(); } return group.getBuyingMaxPrice(); }).reversed()); - case STOCK -> sorted.sort(Comparator.comparingInt(MarketItemGroup::getSellingTotalStock).reversed()); + case STOCK -> + sorted.sort(Comparator.comparingInt(MarketItemGroup::getSellingTotalStock).reversed()); case NAME -> sorted.sort(Comparator.comparing(MarketItemGroup::getItemDisplayName)); } - + return sorted; } - + /** * Search shops by item name - * @param shops List of shops to search + * + * @param shops List of shops to search * @param searchQuery The search query (item name) + * * @return List of shops matching the search query */ @NotNull public static List searchShops(@NotNull final List shops, - @Nullable final String searchQuery) { + @Nullable final String searchQuery) { + if(searchQuery == null || searchQuery.trim().isEmpty()) { return new ArrayList<>(shops); } - + final String query = searchQuery.toLowerCase(Locale.ROOT).trim(); - + return shops.stream() - .filter(shop -> matchesSearch(shop.getItem(), query)) + .filter(shop->matchesSearch(shop.getItem(), query)) .toList(); } - + /** * Search item groups by item name - * @param groups List of groups to search + * + * @param groups List of groups to search * @param searchQuery The search query (item name) + * * @return List of groups matching the search query */ @NotNull public static List searchGroups(@NotNull final List groups, - @Nullable final String searchQuery) { + @Nullable final String searchQuery) { + if(searchQuery == null || searchQuery.trim().isEmpty()) { return new ArrayList<>(groups); } - + final String query = searchQuery.toLowerCase(Locale.ROOT).trim(); - + return groups.stream() - .filter(group -> matchesSearch(group.getRepresentativeItem(), query)) + .filter(group->matchesSearch(group.getRepresentativeItem(), query)) .toList(); } - + /** * Check if an item matches a search query - * @param item The item to check + * + * @param item The item to check * @param query The search query (lowercase) + * * @return true if the item matches */ private static boolean matchesSearch(@NotNull final ItemStack item, @NotNull final String query) { @@ -287,39 +314,40 @@ private static boolean matchesSearch(@NotNull final ItemStack item, @NotNull fin if(materialName.contains(query)) { return true; } - + // Check prettified name final String prettyName = CommonUtil.prettifyText(item.getType().name()).toLowerCase(Locale.ROOT); if(prettyName.contains(query)) { return true; } - + // Check custom display name if present if(item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { final String displayName = item.getItemMeta().getDisplayName().toLowerCase(Locale.ROOT); - if(displayName.contains(query)) { - return true; - } + return displayName.contains(query); } - + return false; } - + /** * Apply all filters, search, and sorting to shops - * @param shops The original list of shops - * @param filterMode The filter mode - * @param sortMode The sort mode + * + * @param shops The original list of shops + * @param filterMode The filter mode + * @param sortMode The sort mode * @param searchQuery The search query (can be null) - * @param stockOnly Whether to only show shops with stock/space + * @param stockOnly Whether to only show shops with stock/space + * * @return Processed list of shops */ @NotNull public static List processShops(@NotNull final List shops, - @NotNull final BrowseFilterMode filterMode, - @NotNull final BrowseSortMode sortMode, - @Nullable final String searchQuery, - final boolean stockOnly) { + @NotNull final BrowseFilterMode filterMode, + @NotNull final BrowseSortMode sortMode, + @Nullable final String searchQuery, + final boolean stockOnly) { + List result = new ArrayList<>(shops); result = filterShops(result, filterMode); result = filterByStock(result, stockOnly); @@ -327,45 +355,48 @@ public static List processShops(@NotNull final List shops, result = sortShops(result, sortMode); return result; } - + /** * Apply all filters, search, and sorting to item groups - * @param shops The original list of shops - * @param filterMode The filter mode - * @param sortMode The sort mode + * + * @param shops The original list of shops + * @param filterMode The filter mode + * @param sortMode The sort mode * @param searchQuery The search query (can be null) - * @param stockOnly Whether to only show groups with stock/space + * @param stockOnly Whether to only show groups with stock/space + * * @return Processed list of item groups */ @NotNull public static List processGroups(@NotNull final List shops, - @NotNull final BrowseFilterMode filterMode, - @NotNull final BrowseSortMode sortMode, - @Nullable final String searchQuery, - final boolean stockOnly) { + @NotNull final BrowseFilterMode filterMode, + @NotNull final BrowseSortMode sortMode, + @Nullable final String searchQuery, + final boolean stockOnly) { // First filter shops, then group them List filteredShops = filterShops(shops, filterMode); filteredShops = filterByStock(filteredShops, stockOnly); filteredShops = searchShops(filteredShops, searchQuery); - + // Group the filtered shops List groups = groupShopsByItem(filteredShops); - + // Sort the groups groups = sortGroups(groups, sortMode); - + return groups; } /** - * Get stock count from database cache. - * This avoids Folia cross-region block access issues by using cached data - * instead of directly accessing the shop's inventory. + * Get stock count from database cache. This avoids Folia cross-region block access issues by + * using cached data instead of directly accessing the shop's inventory. * * @param shop The shop to get stock for + * * @return Stock count, or -1 for unlimited shops, 0 for errors/uninitialized */ public static int getStockFromCache(@NotNull final Shop shop) { + if(shop.isUnlimited()) { return -1; } @@ -374,7 +405,7 @@ public static int getStockFromCache(@NotNull final Shop shop) { .queryShopInventoryCacheInDatabase(shop).join(); final int stock = cache.getStock(); // Return stock if available, otherwise return 0 for uninitialized cache - return stock >= 0?stock : 0; + return stock >= 0? stock : 0; } catch(final Exception e) { // Fallback to 0 if cache query fails return 0; @@ -382,14 +413,15 @@ public static int getStockFromCache(@NotNull final Shop shop) { } /** - * Get space count from database cache. - * This avoids Folia cross-region block access issues by using cached data - * instead of directly accessing the shop's inventory. + * Get space count from database cache. This avoids Folia cross-region block access issues by + * using cached data instead of directly accessing the shop's inventory. * * @param shop The shop to get space for + * * @return Space count, or -1 for unlimited shops, 0 for errors/uninitialized */ public static int getSpaceFromCache(@NotNull final Shop shop) { + if(shop.isUnlimited()) { return -1; } @@ -398,7 +430,7 @@ public static int getSpaceFromCache(@NotNull final Shop shop) { .queryShopInventoryCacheInDatabase(shop).join(); final int space = cache.getSpace(); // Return space if available, otherwise return 0 for uninitialized cache - return space >= 0?space : 0; + return space >= 0? space : 0; } catch(final Exception e) { // Fallback to 0 if cache query fails return 0; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java index 39b6b1f62c..c401fc4d8c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java @@ -54,8 +54,8 @@ import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigLore; /** - * ShopListPage - Shows all shops for a specific item type - * with price comparison and location information + * ShopListPage - Shows all shops for a specific item type with price comparison and location + * information * * @author creatorfromhell * @since 6.2.0.8 @@ -66,11 +66,13 @@ public class ShopListPage { private final int menuRows; public ShopListPage(final String menuName, final int menuRows) { + this.menuName = menuName; this.menuRows = Math.max(menuRows, 3); } public void handle(final PageOpenCallback callback) { + final Optional viewerOpt = callback.getPlayer().viewer(); if(viewerOpt.isEmpty()) return; @@ -87,25 +89,24 @@ public void handle(final PageOpenCallback callback) { // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("browse"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig backConfig = menuConfig != null?menuConfig.getIcon("back") : null; - final GuiConfig.IconConfig itemInfoConfig = menuConfig != null?menuConfig.getIcon("item-info") : null; - final GuiConfig.IconConfig sortConfig = menuConfig != null?menuConfig.getIcon("shop-list-sort") : null; - final GuiConfig.IconConfig closeConfig = menuConfig != null?menuConfig.getIcon("close") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig pageInfoConfig = menuConfig != null?menuConfig.getIcon("page-info") : null; - - final int listStartSlot = menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9; + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig backConfig = menuConfig != null? menuConfig.getIcon("back") : null; + final GuiConfig.IconConfig itemInfoConfig = menuConfig != null? menuConfig.getIcon("item-info") : null; + final GuiConfig.IconConfig sortConfig = menuConfig != null? menuConfig.getIcon("shop-list-sort") : null; + final GuiConfig.IconConfig closeConfig = menuConfig != null? menuConfig.getIcon("close") : null; + final GuiConfig.IconConfig prevPageConfig = menuConfig != null? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = menuConfig != null? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig pageInfoConfig = menuConfig != null? menuConfig.getIcon("page-info") : null; + + final int listStartSlot = menuConfig != null? menuConfig.getSection().getInt("list-start-slot", 9) : 9; // Get current state - final BrowseSortMode sortMode = (BrowseSortMode) viewer.dataOrDefault(BROWSE_SORT, BrowseSortMode.PRICE_ASC); - final BrowseFilterMode filterMode = (BrowseFilterMode) viewer.dataOrDefault(BROWSE_FILTER, BrowseFilterMode.ALL); - final boolean stockOnly = (Boolean) viewer.dataOrDefault(BROWSE_STOCK_ONLY, false); - final int page = (Integer) viewer.dataOrDefault(SHOP_LIST_PAGE, 1); + final BrowseSortMode sortMode = (BrowseSortMode)viewer.dataOrDefault(BROWSE_SORT, BrowseSortMode.PRICE_ASC); + final BrowseFilterMode filterMode = (BrowseFilterMode)viewer.dataOrDefault(BROWSE_FILTER, BrowseFilterMode.ALL); + final boolean stockOnly = (Boolean)viewer.dataOrDefault(BROWSE_STOCK_ONLY, false); + final int page = (Integer)viewer.dataOrDefault(SHOP_LIST_PAGE, 1); - @SuppressWarnings("unchecked") - final List allShops = (ArrayList) shopsData.get(); + @SuppressWarnings("unchecked") final List allShops = (ArrayList)shopsData.get(); // Apply filter and stock filter, then sort List filteredShops = MarketUtils.filterShops(allShops, filterMode); @@ -113,21 +114,21 @@ public void handle(final PageOpenCallback callback) { final List sortedShops = MarketUtils.sortShops(filteredShops, sortMode); // Calculate average price for comparison indicators - final double avgPrice = sortedShops.isEmpty()?0 : - CommonUtil.avg(sortedShops.stream().map(Shop::getPrice).toList()); + final double avgPrice = sortedShops.isEmpty()? 0 : + CommonUtil.avg(sortedShops.stream().map(Shop::getPrice).toList()); // Calculate pagination (same pattern as MainPage) final int offset = 9; final int items = (menuRows - 2) * offset; final int start = ((page - 1) * offset); - final int maxPages = (sortedShops.size() / items) + (((sortedShops.size() % items) > 0)?1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int maxPages = (sortedShops.size() / items) + (((sortedShops.size() % items) > 0)? 1 : 0); + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // Set up border rows - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(1, 6); for(final int row : borderRows) { menuPage.setRow(row, borderBuilder); } @@ -137,8 +138,8 @@ public void handle(final PageOpenCallback callback) { // Uses same slot positions as GroupedItemPage for consistency // Item info - shows what item we're viewing (slot 0 - top left) - final GuiConfig.IconConfig searchConfig = menuConfig != null?menuConfig.getIcon("search") : null; - final int itemInfoSlot = searchConfig != null?searchConfig.getSlot() : 0; + final GuiConfig.IconConfig searchConfig = menuConfig != null? menuConfig.getIcon("search") : null; + final int itemInfoSlot = searchConfig != null? searchConfig.getSlot() : 0; if(!allShops.isEmpty()) { final Shop firstShop = allShops.getFirst(); final String filterIndicator = getFilterIndicator(filterMode); @@ -150,98 +151,98 @@ public void handle(final PageOpenCallback callback) { QuickShop.getInstance().platform().miniMessage().deserialize("Showing: " + filterIndicator + ""), QuickShop.getInstance().platform().miniMessage().deserialize("Shops: " + sortedShops.size() + ""), QuickShop.getInstance().platform().miniMessage().deserialize("Average price: " + formatPrice(avgPrice) + "") - )); - + )); + menuPage.addIcon(new IconBuilder(infoStack).withSlot(itemInfoSlot).build()); } // Sort button (slot 2) - final String sortMaterial = sortConfig != null?sortConfig.getMaterial() : "HOPPER"; - final int sortSlot = sortConfig != null?sortConfig.getSlot() : 2; + final String sortMaterial = sortConfig != null? sortConfig.getMaterial() : "HOPPER"; + final int sortSlot = sortConfig != null? sortConfig.getSlot() : 2; menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(sortMaterial, 1) - .display(getConfigDisplay(sortConfig, "Sort: {0}", getSortDisplayName(sortMode))) - .lore(getConfigLore(sortConfig))) - .withSlot(sortSlot) - .withActions( - new DataAction(BROWSE_SORT, sortMode.next()), - new DataAction(SHOP_LIST_PAGE, 1), - new SwitchPageAction(menuName, 2) - ) - .build()); + .display(getConfigDisplay(sortConfig, "Sort: {0}", getSortDisplayName(sortMode))) + .lore(getConfigLore(sortConfig))) + .withSlot(sortSlot) + .withActions( + new DataAction(BROWSE_SORT, sortMode.next()), + new DataAction(SHOP_LIST_PAGE, 1), + new SwitchPageAction(menuName, 2) + ) + .build()); // Filter button (slot 4) - final GuiConfig.IconConfig filterConfig = menuConfig != null?menuConfig.getIcon("filter") : null; - final String filterMaterial = filterConfig != null?filterConfig.getMaterial() : "NAME_TAG"; - final int filterSlot = filterConfig != null?filterConfig.getSlot() : 4; - + final GuiConfig.IconConfig filterConfig = menuConfig != null? menuConfig.getIcon("filter") : null; + final String filterMaterial = filterConfig != null? filterConfig.getMaterial() : "NAME_TAG"; + final int filterSlot = filterConfig != null? filterConfig.getSlot() : 4; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(filterMaterial, 1) - .display(getConfigDisplay(filterConfig, "Filter: {0}", getFilterDisplayName(filterMode))) - .lore(getConfigLore(filterConfig))) - .withSlot(filterSlot) - .withActions( - new DataAction(BROWSE_FILTER, filterMode.next()), - new DataAction(SHOP_LIST_PAGE, 1), - new SwitchPageAction(menuName, 2) - ) - .build()); + .display(getConfigDisplay(filterConfig, "Filter: {0}", getFilterDisplayName(filterMode))) + .lore(getConfigLore(filterConfig))) + .withSlot(filterSlot) + .withActions( + new DataAction(BROWSE_FILTER, filterMode.next()), + new DataAction(SHOP_LIST_PAGE, 1), + new SwitchPageAction(menuName, 2) + ) + .build()); // Stock filter toggle button (slot 6) - final GuiConfig.IconConfig stockConfig = menuConfig != null?menuConfig.getIcon("stock-filter") : null; - final String stockMaterial = stockConfig != null?stockConfig.getMaterial() : "CHEST"; - final int stockSlot = stockConfig != null?stockConfig.getSlot() : 6; - final String stockStatus = stockOnly?"ON" : "OFF"; - + final GuiConfig.IconConfig stockConfig = menuConfig != null? menuConfig.getIcon("stock-filter") : null; + final String stockMaterial = stockConfig != null? stockConfig.getMaterial() : "CHEST"; + final int stockSlot = stockConfig != null? stockConfig.getSlot() : 6; + final String stockStatus = stockOnly? "ON" : "OFF"; + menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(stockMaterial, 1) - .display(getConfigDisplay(stockConfig, "In Stock Only: {0}", stockStatus)) - .lore(getConfigLore(stockConfig))) - .withSlot(stockSlot) - .withActions( - new DataAction(BROWSE_STOCK_ONLY, !stockOnly), - new DataAction(SHOP_LIST_PAGE, 1), - new SwitchPageAction(menuName, 2) - ) - .build()); + .display(getConfigDisplay(stockConfig, "In Stock Only: {0}", stockStatus)) + .lore(getConfigLore(stockConfig))) + .withSlot(stockSlot) + .withActions( + new DataAction(BROWSE_STOCK_ONLY, !stockOnly), + new DataAction(SHOP_LIST_PAGE, 1), + new SwitchPageAction(menuName, 2) + ) + .build()); // Back button - final String backMaterial = backConfig != null?backConfig.getMaterial() : "OAK_DOOR"; - final int backSlot = backConfig != null?backConfig.getSlot() : 8; + final String backMaterial = backConfig != null? backConfig.getMaterial() : "OAK_DOOR"; + final int backSlot = backConfig != null? backConfig.getSlot() : 8; menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(backMaterial, 1) - .display(getConfigDisplay(backConfig, "Back to Market"))) - .withSlot(backSlot) - .withActions( - new DataAction(SHOPS_PAGE, 1), - new SwitchPageAction(menuName, 1) // Go back to grouped view - ) - .build()); + .display(getConfigDisplay(backConfig, "Back to Market"))) + .withSlot(backSlot) + .withActions( + new DataAction(SHOPS_PAGE, 1), + new SwitchPageAction(menuName, 1) // Go back to grouped view + ) + .build()); // === Pagination Row (Bottom) === - final String prevMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevSlot = prevPageConfig != null?prevPageConfig.getSlot() : 48; - final String nextMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextSlot = nextPageConfig != null?nextPageConfig.getSlot() : 50; - final String pageInfoMaterial = pageInfoConfig != null?pageInfoConfig.getMaterial() : "BOOK"; - final int pageInfoSlot = pageInfoConfig != null?pageInfoConfig.getSlot() : 49; + final String prevMaterial = prevPageConfig != null? prevPageConfig.getMaterial() : "ARROW"; + final int prevSlot = prevPageConfig != null? prevPageConfig.getSlot() : 48; + final String nextMaterial = nextPageConfig != null? nextPageConfig.getMaterial() : "ARROW"; + final int nextSlot = nextPageConfig != null? nextPageConfig.getSlot() : 50; + final String pageInfoMaterial = pageInfoConfig != null? pageInfoConfig.getMaterial() : "BOOK"; + final int pageInfoSlot = pageInfoConfig != null? pageInfoConfig.getSlot() : 49; if(maxPages > 1) { menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(prevMaterial, 1) - .display(getConfigDisplay(prevPageConfig, "<< Previous Page"))) - .withSlot(prevSlot) - .withActions(new DataAction(SHOP_LIST_PAGE, prev), new SwitchPageAction(menuName, 2)) - .build()); + .display(getConfigDisplay(prevPageConfig, "<< Previous Page"))) + .withSlot(prevSlot) + .withActions(new DataAction(SHOP_LIST_PAGE, prev), new SwitchPageAction(menuName, 2)) + .build()); menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(nextMaterial, 1) - .display(getConfigDisplay(nextPageConfig, "Next Page >>"))) - .withSlot(nextSlot) - .withActions(new DataAction(SHOP_LIST_PAGE, next), new SwitchPageAction(menuName, 2)) - .build()); + .display(getConfigDisplay(nextPageConfig, "Next Page >>"))) + .withSlot(nextSlot) + .withActions(new DataAction(SHOP_LIST_PAGE, next), new SwitchPageAction(menuName, 2)) + .build()); } // Page info menuPage.addIcon(new IconBuilder(QuickShop.getInstance().stack().of(pageInfoMaterial, 1) - .display(getConfigDisplay(pageInfoConfig, "Page {0}/{1}", page, Math.max(1, maxPages)))) - .withSlot(pageInfoSlot) - .build()); + .display(getConfigDisplay(pageInfoConfig, "Page {0}/{1}", page, Math.max(1, maxPages)))) + .withSlot(pageInfoSlot) + .build()); // Check if player has teleport permission final boolean canTeleport = QuickShop.getInstance().perm().hasPermission(player, "quickshop.browse.teleport"); @@ -257,7 +258,7 @@ public void handle(final PageOpenCallback callback) { // Build shop lore with price indicator and click instruction final List lore = buildShopLore(shop, avgPrice, canTeleport); - + // Get display name for the item final String itemName = CommonUtil.prettifyText(shop.getItem().getType().name()); @@ -270,17 +271,17 @@ public void handle(final PageOpenCallback callback) { // Note: We avoid calling shop.getSigns() here as it requires block access // which can fail on Folia when the shop is in a different region final Location teleportTarget = shop.getLocation().clone().add(0.5, 1, 0.5); - + final IconBuilder iconBuilder = new IconBuilder(stack) .withSlot(listStartSlot + (i - start)); - + // Only add teleport action if player has permission if(canTeleport) { // Capture for lambda final Location finalTeleportTarget = teleportTarget; final Location shopLoc = shop.getLocation().clone().add(0.5, 0.5, 0.5); - - iconBuilder.withActions(new RunnableAction((click) -> { + + iconBuilder.withActions(new RunnableAction((click)->{ final Player p = Bukkit.getPlayer(click.player().identifier()); if(p != null) { p.closeInventory(); @@ -289,24 +290,25 @@ public void handle(final PageOpenCallback callback) { // Calculate direction to look at shop final double dx = shopLoc.getX() - teleportLoc.getX(); final double dz = shopLoc.getZ() - teleportLoc.getZ(); - teleportLoc.setYaw((float) Math.toDegrees(Math.atan2(-dx, dz))); + teleportLoc.setYaw((float)Math.toDegrees(Math.atan2(-dx, dz))); teleportLoc.setPitch(30); // Slightly looking down PaperLib.teleportAsync(p, teleportLoc, PlayerTeleportEvent.TeleportCause.PLUGIN); } })); } - + menuPage.addIcon(iconBuilder.build()); - + i++; } } /** - * Build the lore for an individual shop. - * Note: Uses database cache for stock/space to avoid Folia cross-region block access issues. + * Build the lore for an individual shop. Note: Uses database cache for stock/space to avoid Folia + * cross-region block access issues. */ private List buildShopLore(final Shop shop, final double avgPrice, final boolean canTeleport) { + final List lore = new ArrayList<>(); final var mm = QuickShop.getInstance().platform().miniMessage(); @@ -314,8 +316,8 @@ private List buildShopLore(final Shop shop, final double avgPrice, fi lore.add(mm.deserialize("Owner: " + shop.getOwner().getDisplay() + "")); // Shop type - final String typeColor = shop.isSelling()?"" : "<#FFA500>"; - final String typeText = shop.isSelling()?"Selling" : "Buying"; + final String typeColor = shop.isSelling()? "" : "<#FFA500>"; + final String typeText = shop.isSelling()? "Selling" : "Buying"; lore.add(mm.deserialize("Type: " + typeColor + typeText + "")); // Price with indicator @@ -327,23 +329,23 @@ private List buildShopLore(final Shop shop, final double avgPrice, fi // The shop may be in a different region than the player viewing the menu if(shop.isSelling()) { final int stock = getStockFromCache(shop); - final String stockText = stock < 0?"Unlimited" : String.valueOf(stock); + final String stockText = stock < 0? "Unlimited" : String.valueOf(stock); lore.add(mm.deserialize("Stock: " + stockText + "")); } else { final int space = getSpaceFromCache(shop); - final String spaceText = space < 0?"Unlimited" : String.valueOf(space); + final String spaceText = space < 0? "Unlimited" : String.valueOf(space); lore.add(mm.deserialize("Space: " + spaceText + "")); } // Location final String world = shop.getLocation().getWorld() != null? - shop.getLocation().getWorld().getName() : "Unknown"; - final String coords = shop.getLocation().getBlockX() + ", " + - shop.getLocation().getBlockY() + ", " + - shop.getLocation().getBlockZ(); + shop.getLocation().getWorld().getName() : "Unknown"; + final String coords = shop.getLocation().getBlockX() + ", " + + shop.getLocation().getBlockY() + ", " + + shop.getLocation().getBlockZ(); lore.add(mm.deserialize("Location: " + world + "")); lore.add(mm.deserialize("" + coords + "")); - + // Click instruction (only if player has teleport permission) if(canTeleport) { lore.add(Component.empty()); @@ -357,10 +359,11 @@ private List buildShopLore(final Shop shop, final double avgPrice, fi * Get a price indicator based on comparison to average */ private String getPriceIndicator(final double price, final double avgPrice, final boolean isSelling) { + if(avgPrice == 0) return ""; - + final double ratio = price / avgPrice; - + if(isSelling) { // For selling shops: lower is better for buyers if(ratio < 0.85) return "▼▼ Great Deal!"; @@ -381,6 +384,7 @@ private String getPriceIndicator(final double price, final double avgPrice, fina * Get color based on price indicator */ private String getPriceColor(final String indicator) { + if(indicator.contains("Great")) return ""; if(indicator.contains("Below") || indicator.contains("Low")) return ""; if(indicator.contains("Above")) return ""; @@ -392,6 +396,7 @@ private String getPriceColor(final String indicator) { * Get display name for sort mode */ private String getSortDisplayName(final BrowseSortMode mode) { + return switch(mode) { case PRICE_ASC -> "Price ↑"; case PRICE_DESC -> "Price ↓"; @@ -404,6 +409,7 @@ private String getSortDisplayName(final BrowseSortMode mode) { * Get display name for filter mode */ private String getFilterDisplayName(final BrowseFilterMode mode) { + return switch(mode) { case ALL -> "All"; case BUYING -> "Buying"; @@ -415,6 +421,7 @@ private String getFilterDisplayName(final BrowseFilterMode mode) { * Get colored indicator for current filter mode */ private String getFilterIndicator(final BrowseFilterMode mode) { + return switch(mode) { case ALL -> "All Shops"; case BUYING -> "<#FFA500>Buying Shops"; @@ -426,31 +433,34 @@ private String getFilterIndicator(final BrowseFilterMode mode) { * Format a price value */ private String formatPrice(final double price) { + return QuickShop.getInstance().getEconomyManager().provider() .format(BigDecimal.valueOf(price), null, null); } /** - * Get stock count from database cache. - * This avoids Folia cross-region block access issues by using cached data - * instead of directly accessing the shop's inventory. + * Get stock count from database cache. This avoids Folia cross-region block access issues by + * using cached data instead of directly accessing the shop's inventory. * * @param shop The shop to get stock for + * * @return Stock count, or -1 for unlimited shops */ private int getStockFromCache(final Shop shop) { + return MarketUtils.getStockFromCache(shop); } /** - * Get space count from database cache. - * This avoids Folia cross-region block access issues by using cached data - * instead of directly accessing the shop's inventory. + * Get space count from database cache. This avoids Folia cross-region block access issues by + * using cached data instead of directly accessing the shop's inventory. * * @param shop The shop to get space for + * * @return Space count, or -1 for unlimited shops */ private int getSpaceFromCache(final Shop shop) { + return MarketUtils.getSpaceFromCache(shop); } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java index 6aa38d5a1f..d301db333f 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java @@ -45,16 +45,18 @@ public class GuiConfig implements Reloadable { private final QuickShop plugin; - private FileConfiguration config; private final Map menuConfigs = new HashMap<>(); + private FileConfiguration config; public GuiConfig(@NotNull final QuickShop plugin) { + this.plugin = plugin; loadConfig(); plugin.getReloadManager().register(this); } public void loadConfig() { + Log.debug("GUI Config Loading."); final File configFile = new File(plugin.getDataFolder(), "gui.yml"); @@ -79,6 +81,7 @@ public void loadConfig() { } private void loadMenuConfig(final String menuName) { + final ConfigurationSection section = config.getConfigurationSection(menuName); if(section != null) { menuConfigs.put(menuName, new MenuConfig(section)); @@ -87,23 +90,27 @@ private void loadMenuConfig(final String menuName) { @Nullable public MenuConfig getMenuConfig(final String menuName) { + return menuConfigs.get(menuName); } @NotNull public FileConfiguration getConfig() { + return config; } /** * Gets a chat message from the gui.yml messages section. * - * @param path The path under "messages" section (e.g., "keeper.confirm-delete") + * @param path The path under "messages" section (e.g., "keeper.confirm-delete") * @param defaultValue The default value if not found + * * @return The message string */ @NotNull public String getMessage(@NotNull final String path, @NotNull final String defaultValue) { + return config.getString("messages." + path, defaultValue); } @@ -111,15 +118,18 @@ public String getMessage(@NotNull final String path, @NotNull final String defau * Gets a chat message from the gui.yml messages section. * * @param path The path under "messages" section (e.g., "keeper.confirm-delete") + * * @return The message string, or the path if not found */ @NotNull public String getMessage(@NotNull final String path) { + return config.getString("messages." + path, path); } @Override public ReloadResult reloadModule() throws Exception { + loadConfig(); plugin.logger().info("GUI configuration reloaded successfully. Menus loaded: " + menuConfigs.keySet()); plugin.logger().info("Note: Changes to menu row counts require a server restart to take effect."); @@ -130,123 +140,138 @@ public ReloadResult reloadModule() throws Exception { * Represents configuration for a single menu */ public static class MenuConfig { + private final ConfigurationSection section; private final Map icons = new HashMap<>(); public MenuConfig(final ConfigurationSection section) { + this.section = section; loadIcons(); } private void loadIcons() { + for(final String key : section.getKeys(false)) { if(key.equals("title") || key.equals("rows")) continue; - + final Object value = section.get(key); - if(value instanceof ConfigurationSection iconSection) { + if(value instanceof final ConfigurationSection iconSection) { icons.put(key, new IconConfig(iconSection)); } } } public String getTitle() { + return section.getString("title", "Menu"); } public int getRows() { + return section.getInt("rows", 6); } @Nullable public IconConfig getIcon(final String iconName) { + return icons.get(iconName); } @NotNull public ConfigurationSection getSection() { + return section; } } /** - * Represents configuration for a single icon/button - */ - public static class IconConfig { - private final ConfigurationSection section; - - public IconConfig(final ConfigurationSection section) { - this.section = section; - } + * Represents configuration for a single icon/button + */ + public record IconConfig(ConfigurationSection section) { @NotNull - public String getMaterial() { - return section.getString("material", "STONE"); - } + public String getMaterial() { - @Nullable - public String getName() { - return section.getString("name"); - } + return section.getString("material", "STONE"); + } - @NotNull - public List getLore() { - final Object loreObj = section.get("lore"); - if(loreObj instanceof List) { - final List result = new ArrayList<>(); - for(final Object item : (List) loreObj) { - if(item != null) { - result.add(item.toString()); + @Nullable + public String getName() { + + return section.getString("name"); + } + + @NotNull + public List getLore() { + + final Object loreObj = section.get("lore"); + if(loreObj instanceof List) { + final List result = new ArrayList<>(); + for(final Object item : (List)loreObj) { + if(item != null) { + result.add(item.toString()); + } } + return result; + } else if(loreObj instanceof String) { + final List result = new ArrayList<>(); + result.add((String)loreObj); + return result; } - return result; - } else if(loreObj instanceof String) { - final List result = new ArrayList<>(); - result.add((String) loreObj); - return result; + return new ArrayList<>(); } - return new ArrayList<>(); - } - public int getCustomModelData() { - return section.getInt("custom-model-data", 0); - } + public int getCustomModelData() { - public int getSlot() { - return section.getInt("slot", 0); - } + return section.getInt("custom-model-data", 0); + } - public int getAmount() { - return section.getInt("amount", 1); - } + public int getSlot() { - @NotNull - public List getSlots() { - return section.getIntegerList("slots"); - } + return section.getInt("slot", 0); + } - @NotNull - public List getQuantities() { - return section.getIntegerList("quantities"); - } + public int getAmount() { - @NotNull - public List getRows() { - return section.getIntegerList("rows"); - } + return section.getInt("amount", 1); + } - public boolean hasCustomModelData() { - return section.contains("custom-model-data") && getCustomModelData() != 0; - } + @NotNull + public List getSlots() { - @Nullable - public ConfigurationSection getSection() { - return section; - } + return section.getIntegerList("slots"); + } - @Nullable - public IconConfig getSubIcon(final String name) { - final ConfigurationSection sub = section.getConfigurationSection(name); - return sub != null?new IconConfig(sub) : null; + @NotNull + public List getQuantities() { + + return section.getIntegerList("quantities"); + } + + @NotNull + public List getRows() { + + return section.getIntegerList("rows"); + } + + public boolean hasCustomModelData() { + + return section.contains("custom-model-data") && getCustomModelData() != 0; + } + + @Override + @Nullable + public ConfigurationSection section() { + + return section; + } + + @Nullable + public IconConfig getSubIcon(final String name) { + + final ConfigurationSection sub = section.getConfigurationSection(name); + return sub != null? new IconConfig(sub) : null; + } } - } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java index 1418115e7b..b849d63b97 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java @@ -40,10 +40,12 @@ public class GuiIconBuilder { * Creates an AbstractItemStack from IconConfig * * @param config The icon configuration + * * @return The configured AbstractItemStack */ @NotNull public static AbstractItemStack createStack(@NotNull final GuiConfig.IconConfig config) { + AbstractItemStack stack = QuickShop.getInstance().stack() .of(config.getMaterial(), config.getAmount()); @@ -77,10 +79,12 @@ public static AbstractItemStack createStack(@NotNull final GuiConfig.IconConf * Creates an IconBuilder from IconConfig with slot * * @param config The icon configuration + * * @return The configured IconBuilder */ @NotNull public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig config) { + return new IconBuilder(createStack(config)) .withSlot(config.getSlot()); } @@ -90,11 +94,13 @@ public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig * * @param config The icon configuration * @param slot The slot to use + * * @return The configured IconBuilder */ @NotNull public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig config, - final int slot) { + final int slot) { + return new IconBuilder(createStack(config)) .withSlot(slot); } @@ -102,18 +108,20 @@ public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig /** * Creates an IconBuilder with custom display name and lore * - * @param config The icon configuration (for material and custom model data) - * @param slot The slot to use - * @param name The display name component - * @param lore The lore components + * @param config The icon configuration (for material and custom model data) + * @param slot The slot to use + * @param name The display name component + * @param lore The lore components + * * @return The configured IconBuilder */ @NotNull public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig config, - final int slot, - @NotNull final Component name, - @NotNull final List lore) { - AbstractItemStack stack = QuickShop.getInstance().stack() + final int slot, + @NotNull final Component name, + @NotNull final List lore) { + + final AbstractItemStack stack = QuickShop.getInstance().stack() .of(config.getMaterial(), config.getAmount()) .display(name) .lore(lore); @@ -127,13 +135,15 @@ public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig * @param config The icon configuration * @param slot The slot to use * @param name The display name component + * * @return The configured IconBuilder */ @NotNull public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig config, - final int slot, - @NotNull final Component name) { - AbstractItemStack stack = QuickShop.getInstance().stack() + final int slot, + @NotNull final Component name) { + + final AbstractItemStack stack = QuickShop.getInstance().stack() .of(config.getMaterial(), config.getAmount()) .display(name); @@ -144,10 +154,12 @@ public static IconBuilder createIconBuilder(@NotNull final GuiConfig.IconConfig * Creates a border IconBuilder * * @param config The border icon configuration + * * @return The configured IconBuilder for borders */ @NotNull public static IconBuilder createBorderBuilder(@NotNull final GuiConfig.IconConfig config) { + AbstractItemStack stack = QuickShop.getInstance().stack() .of(config.getMaterial(), 1); @@ -166,11 +178,13 @@ public static IconBuilder createBorderBuilder(@NotNull final GuiConfig.IconConfi * * @param item The ItemStack to modify * @param config The icon configuration + * * @return The modified ItemStack */ @NotNull public static ItemStack applyCustomModelData(@NotNull final ItemStack item, - @NotNull final GuiConfig.IconConfig config) { + @NotNull final GuiConfig.IconConfig config) { + if(config.hasCustomModelData()) { final ItemMeta meta = item.getItemMeta(); if(meta != null) { @@ -185,10 +199,12 @@ public static ItemStack applyCustomModelData(@NotNull final ItemStack item, * Parses a string as MiniMessage format directly. * * @param text The MiniMessage formatted text (e.g., "Buy Items") + * * @return The parsed Component */ @NotNull private static Component parseMiniMessage(@NotNull final String text) { + return QuickShop.getInstance().platform().miniMessage().deserialize(text); } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java index 03bc282e40..c020791745 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java @@ -27,6 +27,7 @@ import com.ghostchu.quickshop.shop.history.ShopHistory; import com.ghostchu.quickshop.util.Util; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; import net.tnemc.item.AbstractItemStack; import net.tnemc.item.bukkit.BukkitItemStack; import net.tnemc.item.providers.SkullProfile; @@ -56,7 +57,6 @@ import static com.ghostchu.quickshop.menu.shared.QuickShopPage.get; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigDisplay; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigLore; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getList; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getPlayer; /** @@ -89,7 +89,7 @@ public MainPage(final String returnMenu, final String menuName, this.actions = actions; //we need a controller row and then at least one row for items. - this.menuRows = (menuRows <= 1)?2 : menuRows; + this.menuRows = (menuRows <= 1)? 2 : menuRows; } public void handle(final PageOpenCallback callback) { @@ -110,16 +110,16 @@ public void handle(final PageOpenCallback callback) { // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("history"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig summaryConfig = menuConfig != null?menuConfig.getIcon("summary") : null; - final GuiConfig.IconConfig shopInfoConfig = menuConfig != null?menuConfig.getIcon("shop-info") : null; - final GuiConfig.IconConfig multiShopConfig = menuConfig != null?menuConfig.getIcon("multi-shop-info") : null; - final GuiConfig.IconConfig topCustomersConfig = menuConfig != null?menuConfig.getIcon("top-customers") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig backConfig = menuConfig != null?menuConfig.getIcon("back") : null; + final GuiConfig.IconConfig borderConfig = (menuConfig != null)? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig summaryConfig = (menuConfig != null)? menuConfig.getIcon("summary") : null; + final GuiConfig.IconConfig shopInfoConfig = (menuConfig != null)? menuConfig.getIcon("shop-info") : null; + final GuiConfig.IconConfig multiShopConfig = (menuConfig != null)? menuConfig.getIcon("multi-shop-info") : null; + final GuiConfig.IconConfig topCustomersConfig = (menuConfig != null)? menuConfig.getIcon("top-customers") : null; + final GuiConfig.IconConfig prevPageConfig = (menuConfig != null)? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = (menuConfig != null)? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig backConfig = (menuConfig != null)? menuConfig.getIcon("back") : null; - final int listStartSlot = menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9; + final int listStartSlot = (menuConfig != null)? menuConfig.getSection().getInt("list-start-slot", 9) : 9; final int offset = 9; final int page = (Integer)viewer.get().dataOrDefault(staffPageID, 1); @@ -130,22 +130,22 @@ public void handle(final PageOpenCallback callback) { final List queryResult = (List)historyData.get(); final ShopHistory.ShopSummary summary = (ShopHistory.ShopSummary)summaryData.get(); - final int maxPages = (queryResult.size() / items) + (((queryResult.size() % items) > 0)?1 : 0); + final int maxPages = (queryResult.size() / items) + (((queryResult.size() % items) > 0)? 1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // Set up borders from config - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = (borderConfig != null)? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); - for (final int row : borderRows) { + final List borderRows = (borderConfig != null)? borderConfig.getRows() : List.of(1, 6); + for(final int row : borderRows) { callback.getPage().setRow(row, borderBuilder); } //header icon final Shop shop = shops.getFirst(); - final String world = (shop.getLocation().getWorld() != null)?shop.getLocation().getWorld().getName() : "World"; + final String world = (shop.getLocation().getWorld() != null)? shop.getLocation().getWorld().getName() : "World"; final Component shopName; if(shop.getShopName() != null) { @@ -158,10 +158,10 @@ public void handle(final PageOpenCallback callback) { final Component shopType = QuickShop.getInstance().text().of(shop.shopType().translationKey()).forLocale(); // Shop info icon from config - final String shopInfoMaterial = shopInfoConfig != null?shopInfoConfig.getMaterial() : "PLAYER_HEAD"; - final int shopInfoSlot = shopInfoConfig != null?shopInfoConfig.getSlot() : 4; - final String multiShopMaterial = multiShopConfig != null?multiShopConfig.getMaterial() : "CHEST"; - final int multiShopSlot = multiShopConfig != null?multiShopConfig.getSlot() : 4; + final String shopInfoMaterial = (shopInfoConfig != null)? shopInfoConfig.getMaterial() : "PLAYER_HEAD"; + final int shopInfoSlot = (shopInfoConfig != null)? shopInfoConfig.getSlot() : 4; + final String multiShopMaterial = (multiShopConfig != null)? multiShopConfig.getMaterial() : "CHEST"; + final int multiShopSlot = (multiShopConfig != null)? multiShopConfig.getSlot() : 4; if(shops.size() == 1) { @@ -176,13 +176,13 @@ public void handle(final PageOpenCallback callback) { callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(shopInfoMaterial, 1) .display(shopName) .lore(getConfigLore(shopInfoConfig, - shopType, - shop.getOwner().getDisplay(), - Util.getItemStackName(shop.getItem()), - shop.getPrice(), shop.getShopStackingAmount(), - shop.getLocation().getWorld().getName() + " " + shop.getLocation().getBlockX() - + ", " + shop.getLocation().getBlockY() + ", " - + shop.getLocation().getBlockZ())) + shopType, + shop.getOwner().getDisplay(), + Util.getItemStackName(shop.getItem()), + shop.getPrice(), shop.getShopStackingAmount(), + shop.getLocation().getWorld().getName() + " " + shop.getLocation().getBlockX() + + ", " + shop.getLocation().getBlockY() + ", " + + shop.getLocation().getBlockZ())) .profile(ownerProfile)) .withActions(new SwitchPageAction(returnMenu, returnPage)) .withSlot(shopInfoSlot) @@ -196,8 +196,8 @@ public void handle(final PageOpenCallback callback) { } // Summary icon from config - final String summaryMaterial = summaryConfig != null?summaryConfig.getMaterial() : "OAK_SIGN"; - final int summarySlot = summaryConfig != null?summaryConfig.getSlot() : 0; + final String summaryMaterial = (summaryConfig != null)? summaryConfig.getMaterial() : "OAK_SIGN"; + final int summarySlot = (summaryConfig != null)? summaryConfig.getSlot() : 0; final List description = new ArrayList<>(); description.add(get(id, "history.shop.total-unique-purchasers", locale.getNumberFormat().format(summary.uniquePurchasers()))); @@ -219,8 +219,8 @@ public void handle(final PageOpenCallback callback) { .build()); // Top customers icon from config - final String topCustomersMaterial = topCustomersConfig != null?topCustomersConfig.getMaterial() : "DIAMOND"; - final int topCustomersSlot = topCustomersConfig != null?topCustomersConfig.getSlot() : 8; + final String topCustomersMaterial = (topCustomersConfig != null)? topCustomersConfig.getMaterial() : "DIAMOND"; + final int topCustomersSlot = (topCustomersConfig != null)? topCustomersConfig.getSlot() : 8; final List valuableDescription = new ArrayList<>(summary.valuableCustomers().size()); for(final Map.Entry entry : summary.valuableCustomers().entrySet()) { @@ -234,10 +234,10 @@ public void handle(final PageOpenCallback callback) { .lore(valuableDescription)).withSlot(topCustomersSlot).build()); // Pagination icons from config - final String prevPageMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevPageSlot = prevPageConfig != null?prevPageConfig.getSlot() : 3; - final String nextPageMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextPageSlot = nextPageConfig != null?nextPageConfig.getSlot() : 5; + final String prevPageMaterial = (prevPageConfig != null)? prevPageConfig.getMaterial() : "ARROW"; + final int prevPageSlot = (prevPageConfig != null)? prevPageConfig.getSlot() : 3; + final String nextPageMaterial = (nextPageConfig != null)? nextPageConfig.getMaterial() : "ARROW"; + final int nextPageSlot = (nextPageConfig != null)? nextPageConfig.getSlot() : 5; if(maxPages > 1) { @@ -298,12 +298,12 @@ public void handle(final PageOpenCallback callback) { final String timeFormat = "yyyy-MM-dd HH:mm"; final SimpleDateFormat format = new SimpleDateFormat(timeFormat); final String dateStr = format.format(record.date()); - + // Build lore dynamically - this is transaction data that needs to be shown final List lore = new ArrayList<>(); - lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Shop: " + shopName + "")); + lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Shop: {0} ").replaceText(TextReplacementConfig.builder().match("\\{0\\}").replacement(shopName).build())); lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Buyer: " + userName + "")); - lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Item: " + itemName + "")); + lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Item: {0} ").replaceText(TextReplacementConfig.builder().match("\\{0\\}").replacement(itemName).build())); lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Amount: " + record.amount() + "")); lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Total: " + record.money() + "")); lore.add(QuickShop.getInstance().platform().miniMessage().deserialize("Tax: " + record.tax() + "")); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java index 486cadf7c5..5d3bd08dfc 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java @@ -29,7 +29,6 @@ import com.ghostchu.quickshop.util.Util; import com.ghostchu.quickshop.util.logger.Log; import com.ghostchu.quickshop.util.logging.container.ShopRemoveLog; -import net.kyori.adventure.text.Component; import net.tnemc.item.AbstractItemStack; import net.tnemc.item.bukkit.BukkitItemStack; import net.tnemc.item.providers.SkullProfile; @@ -55,7 +54,6 @@ import static com.ghostchu.quickshop.menu.ShopHistoryMenu.HISTORY_RECORDS; import static com.ghostchu.quickshop.menu.ShopHistoryMenu.HISTORY_SUMMARY; import static com.ghostchu.quickshop.menu.ShopHistoryMenu.SHOPS_DATA; - import static com.ghostchu.quickshop.menu.ShopKeeperMenu.KEEPER_MAIN; import static com.ghostchu.quickshop.shop.SimpleShopManager.BUYING_TYPE; import static com.ghostchu.quickshop.shop.SimpleShopManager.FROZEN_TYPE; @@ -92,37 +90,37 @@ public void open(final PageOpenCallback open) { // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("keeper"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig shopItemConfig = menuConfig != null?menuConfig.getIcon("shop-item") : null; - final GuiConfig.IconConfig changePriceConfig = menuConfig != null?menuConfig.getIcon("change-price") : null; - final GuiConfig.IconConfig modeToggleConfig = menuConfig != null?menuConfig.getIcon("mode-toggle") : null; - final GuiConfig.IconConfig staffConfig = menuConfig != null?menuConfig.getIcon("staff") : null; - final GuiConfig.IconConfig historyConfig = menuConfig != null?menuConfig.getIcon("history") : null; - final GuiConfig.IconConfig removeConfig = menuConfig != null?menuConfig.getIcon("remove") : null; - final GuiConfig.IconConfig closeConfig = menuConfig != null?menuConfig.getIcon("close") : null; + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig shopItemConfig = menuConfig != null? menuConfig.getIcon("shop-item") : null; + final GuiConfig.IconConfig changePriceConfig = menuConfig != null? menuConfig.getIcon("change-price") : null; + final GuiConfig.IconConfig modeToggleConfig = menuConfig != null? menuConfig.getIcon("mode-toggle") : null; + final GuiConfig.IconConfig staffConfig = menuConfig != null? menuConfig.getIcon("staff") : null; + final GuiConfig.IconConfig historyConfig = menuConfig != null? menuConfig.getIcon("history") : null; + final GuiConfig.IconConfig removeConfig = menuConfig != null? menuConfig.getIcon("remove") : null; + final GuiConfig.IconConfig closeConfig = menuConfig != null? menuConfig.getIcon("close") : null; // Set up our borders from config (gray for modern look) - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - + // Rows 2 and 4 for modern 4-row layout - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(2, 4); - for (final int row : borderRows) { + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(2, 4); + for(final int row : borderRows) { open.getPage().setRow(row, borderBuilder); } // Shop item preview - top center (slot 4) final ItemStack shopItem = shop.get().getItem(); - final int shopItemSlot = shopItemConfig != null?shopItemConfig.getSlot() : 4; + final int shopItemSlot = shopItemConfig != null? shopItemConfig.getSlot() : 4; open.getPage().addIcon(new IconBuilder(new BukkitItemStack().of(shopItem)).withSlot(shopItemSlot).build()); // Always read price directly from shop to get the latest value final double currentPrice = shop.get().getPrice(); // Change price icon from config (GOLD_NUGGET for "price") - final String changePriceMaterial = changePriceConfig != null?changePriceConfig.getMaterial() : "GOLD_NUGGET"; - final int changePriceSlot = changePriceConfig != null?changePriceConfig.getSlot() : 19; - + final String changePriceMaterial = changePriceConfig != null? changePriceConfig.getMaterial() : "GOLD_NUGGET"; + final int changePriceSlot = changePriceConfig != null? changePriceConfig.getSlot() : 19; + if(shop.get().playerAuthorize(id, BuiltInShopPermission.SET_PRICE) || QuickShop.getInstance().perm().hasPermission(player, "quickshop.other.price")) { open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(changePriceMaterial, 1) @@ -148,14 +146,14 @@ public void open(final PageOpenCallback open) { } // Mode Toggle Icon from config (concrete for clean look) - final GuiConfig.IconConfig sellingConfig = modeToggleConfig != null?modeToggleConfig.getSubIcon("selling") : null; - final GuiConfig.IconConfig buyingConfig = modeToggleConfig != null?modeToggleConfig.getSubIcon("buying") : null; - final GuiConfig.IconConfig frozenConfig = modeToggleConfig != null?modeToggleConfig.getSubIcon("frozen") : null; - final String sellingMaterial = sellingConfig != null?sellingConfig.getMaterial() : "LIME_CONCRETE"; - final String buyingMaterial = buyingConfig != null?buyingConfig.getMaterial() : "ORANGE_CONCRETE"; - final String frozenMaterial = frozenConfig != null?frozenConfig.getMaterial() : "LIGHT_BLUE_CONCRETE"; - final int modeToggleSlot = modeToggleConfig != null?modeToggleConfig.getSlot() : 21; - + final GuiConfig.IconConfig sellingConfig = modeToggleConfig != null? modeToggleConfig.getSubIcon("selling") : null; + final GuiConfig.IconConfig buyingConfig = modeToggleConfig != null? modeToggleConfig.getSubIcon("buying") : null; + final GuiConfig.IconConfig frozenConfig = modeToggleConfig != null? modeToggleConfig.getSubIcon("frozen") : null; + final String sellingMaterial = sellingConfig != null? sellingConfig.getMaterial() : "LIME_CONCRETE"; + final String buyingMaterial = buyingConfig != null? buyingConfig.getMaterial() : "ORANGE_CONCRETE"; + final String frozenMaterial = frozenConfig != null? frozenConfig.getMaterial() : "LIGHT_BLUE_CONCRETE"; + final int modeToggleSlot = modeToggleConfig != null? modeToggleConfig.getSlot() : 21; + if(shop.get().playerAuthorize(id, BuiltInShopPermission.SET_SHOPTYPE) || QuickShop.getInstance().perm().hasPermission(player, "quickshop.other.freeze") && QuickShop.getInstance().perm().hasPermission(player, "quickshop.other.sell") @@ -198,9 +196,9 @@ public void open(final PageOpenCallback open) { } // Staff Icon from config - final String staffMaterial = staffConfig != null?staffConfig.getMaterial() : "PLAYER_HEAD"; - final int staffSlot = staffConfig != null?staffConfig.getSlot() : 23; - + final String staffMaterial = staffConfig != null? staffConfig.getMaterial() : "PLAYER_HEAD"; + final int staffSlot = staffConfig != null? staffConfig.getSlot() : 23; + SkullProfile profile = null; if(shop.get().getOwner().isRealPlayer()) { profile = new SkullProfile(); @@ -225,59 +223,59 @@ public void open(final PageOpenCallback open) { } // History Icon from config (NEW - quick access to history) - final String historyMaterial = historyConfig != null?historyConfig.getMaterial() : "BOOK"; - final int historySlot = historyConfig != null?historyConfig.getSlot() : 24; - + final String historyMaterial = historyConfig != null? historyConfig.getMaterial() : "BOOK"; + final int historySlot = historyConfig != null? historyConfig.getSlot() : 24; + if(shop.get().playerAuthorize(id, BuiltInShopPermission.VIEW_PURCHASE_LOGS) || QuickShop.getInstance().perm().hasPermission(player, "quickshop.other.history")) { open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(historyMaterial, 1) - .display(getConfigDisplay(historyConfig, "Transaction History")) - .lore(getConfigLore(historyConfig))) - .withActions(new RunnableAction((click) -> { - // Close current menu and load history async - viewer.get().close(QuickShop.getInstance().createMenuPlayer(player)); - - final List shops = new ArrayList<>(); - shops.add(shop.get()); - - final MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(player); - - Util.asyncThreadRun(() -> { - final ShopHistory shopHistory = new ShopHistory(QuickShop.getInstance(), shops); - - try { - final List queryResult = shopHistory.query(); - final ShopHistory.ShopSummary summary = shopHistory.generateSummary().join(); - Log.debug(summary.toString()); - - if(queryResult == null) { - return; - } - - final MenuViewer historyViewer = new MenuViewer(id); - MenuManager.instance().addViewer(historyViewer); - historyViewer.addData(SHOPS_DATA, shops); - historyViewer.addData(HISTORY_RECORDS, queryResult); - historyViewer.addData(HISTORY_SUMMARY, summary); - - Util.mainThreadRun(() -> { - MenuManager.instance().open("qs:history", 1, menuPlayer); - }); - - } catch(final Exception e) { - MenuManager.instance().removeViewer(id); - QuickShop.getInstance().text().of(id, "internal-error", id).send(); - QuickShop.getInstance().logger().error("Couldn't query the shop history for shops {}.", shopHistory.shops(), e); - } - }); - })) - .withSlot(historySlot).build()); + .display(getConfigDisplay(historyConfig, "Transaction History")) + .lore(getConfigLore(historyConfig))) + .withActions(new RunnableAction((click)->{ + // Close current menu and load history async + viewer.get().close(QuickShop.getInstance().createMenuPlayer(player)); + + final List shops = new ArrayList<>(); + shops.add(shop.get()); + + final MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(player); + + Util.asyncThreadRun(()->{ + final ShopHistory shopHistory = new ShopHistory(QuickShop.getInstance(), shops); + + try { + final List queryResult = shopHistory.query(); + final ShopHistory.ShopSummary summary = shopHistory.generateSummary().join(); + Log.debug(summary.toString()); + + if(queryResult == null) { + return; + } + + final MenuViewer historyViewer = new MenuViewer(id); + MenuManager.instance().addViewer(historyViewer); + historyViewer.addData(SHOPS_DATA, shops); + historyViewer.addData(HISTORY_RECORDS, queryResult); + historyViewer.addData(HISTORY_SUMMARY, summary); + + Util.mainThreadRun(()->{ + MenuManager.instance().open("qs:history", 1, menuPlayer); + }); + + } catch(final Exception e) { + MenuManager.instance().removeViewer(id); + QuickShop.getInstance().text().of(id, "internal-error", id).send(); + QuickShop.getInstance().logger().error("Couldn't query the shop history for shops {}.", shopHistory.shops(), e); + } + }); + })) + .withSlot(historySlot).build()); } // Remove Icon from config (TNT for dramatic effect) - final String removeMaterial = removeConfig != null?removeConfig.getMaterial() : "TNT"; - final int removeSlot = removeConfig != null?removeConfig.getSlot() : 25; - + final String removeMaterial = removeConfig != null? removeConfig.getMaterial() : "TNT"; + final int removeSlot = removeConfig != null? removeConfig.getSlot() : 25; + if(shop.get().playerAuthorize(id, BuiltInShopPermission.DELETE) || QuickShop.getInstance().perm().hasPermission(player, "quickshop.other.destroy")) { open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(removeMaterial, 1) @@ -299,12 +297,12 @@ public void open(final PageOpenCallback open) { } // Close button - OAK_DOOR for "exit" (slot 31) - final String closeMaterial = closeConfig != null?closeConfig.getMaterial() : "OAK_DOOR"; - final int closeSlot = closeConfig != null?closeConfig.getSlot() : 31; + final String closeMaterial = closeConfig != null? closeConfig.getMaterial() : "OAK_DOOR"; + final int closeSlot = closeConfig != null? closeConfig.getSlot() : 31; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(closeMaterial, 1) - .display(getConfigDisplay(closeConfig, "Close"))) - .withActions(new RunnableAction((click -> viewer.get().close(QuickShop.getInstance().createMenuPlayer(player))))) - .withSlot(closeSlot).build()); + .display(getConfigDisplay(closeConfig, "Close"))) + .withActions(new RunnableAction((click->viewer.get().close(QuickShop.getInstance().createMenuPlayer(player))))) + .withSlot(closeSlot).build()); } } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/ClearSearchAction.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/ClearSearchAction.java index 90ad20e796..f62f2fc608 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/ClearSearchAction.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/ClearSearchAction.java @@ -45,6 +45,7 @@ public class ClearSearchAction extends IconAction { */ public ClearSearchAction(final String searchDataKey, final String pageDataKey, final String menuName, final int menuPage) { + super(ActionType.RIGHT_CLICK); this.searchDataKey = searchDataKey; this.pageDataKey = pageDataKey; @@ -58,15 +59,15 @@ public boolean onClick(final MenuClickHandler handler) { final java.util.Optional viewerOpt = handler.player().viewer(); if(viewerOpt.isPresent()) { final MenuViewer viewer = viewerOpt.get(); - + // Clear search and reset page on existing viewer viewer.addData(searchDataKey, ""); viewer.addData(pageDataKey, 1); - + // Switch to same page to refresh (this triggers page open callback) handler.player().inventory().openMenu(handler.player(), menuName, menuPage); } - + return true; } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatAction.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatAction.java index c2d71919b6..01b5d786d3 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatAction.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatAction.java @@ -30,9 +30,9 @@ import java.util.function.Function; /** - * A custom icon action that properly handles chat input by using QuickShop's - * GuiChatInputManager, which cancels the chat event so messages don't appear - * in public chat. Automatically re-opens the menu after input is processed. + * A custom icon action that properly handles chat input by using QuickShop's GuiChatInputManager, + * which cancels the chat event so messages don't appear in public chat. Automatically re-opens the + * menu after input is processed. * * @author creatorfromhell * @since 6.2.0.11 @@ -44,21 +44,22 @@ public class GuiChatAction extends IconAction { private final boolean reopenMenu; /** - * Creates a new GuiChatAction with default ANY action type. - * Will attempt to re-open the current menu after input. + * Creates a new GuiChatAction with default ANY action type. Will attempt to re-open the current + * menu after input. * - * @param handler The handler function. Returns true if input is accepted (stop listening), - * false to keep waiting for more input. + * @param handler The handler function. Returns true if input is accepted (stop listening), false + * to keep waiting for more input. * @param prompt The prompt message to send to the player (supports legacy color codes) */ - public GuiChatAction(@NotNull final Function handler, + public GuiChatAction(@NotNull final Function handler, @Nullable final String prompt) { + this(handler, prompt, true, ActionType.ANY); } /** - * Creates a new GuiChatAction with specified action type. - * Will attempt to re-open the current menu after input. + * Creates a new GuiChatAction with specified action type. Will attempt to re-open the current + * menu after input. * * @param handler The handler function * @param prompt The prompt message @@ -67,6 +68,7 @@ public GuiChatAction(@NotNull final Function handler, public GuiChatAction(@NotNull final Function handler, @Nullable final String prompt, @NotNull final ActionType actionType) { + this(handler, prompt, true, actionType); } @@ -82,6 +84,7 @@ public GuiChatAction(@NotNull final Function handler, @Nullable final String prompt, final boolean reopenMenu, @NotNull final ActionType actionType) { + super(actionType); this.handler = handler; this.prompt = prompt; @@ -98,11 +101,13 @@ public GuiChatAction(@NotNull final Function handler, public GuiChatAction(@NotNull final Function handler, @Nullable final String prompt, final boolean reopenMenu) { + this(handler, prompt, reopenMenu, ActionType.ANY); } @Override public boolean onClick(final MenuClickHandler clickHandler) { + final Player bukkitPlayer = Bukkit.getPlayer(clickHandler.player().identifier()); if(bukkitPlayer == null) { return false; @@ -111,7 +116,7 @@ public boolean onClick(final MenuClickHandler clickHandler) { // Get current menu context for re-opening (only if reopenMenu is true) String currentMenu = null; int currentPage = 1; - + if(reopenMenu) { final Optional viewer = clickHandler.player().viewer(); if(viewer.isPresent()) { @@ -126,16 +131,16 @@ public boolean onClick(final MenuClickHandler clickHandler) { // setting a chatHandler would cause NPE in TNMS's chat listener. // Pass menu context for re-opening after input GuiChatInputManager.getInstance().requestInput( - bukkitPlayer, - handler, + bukkitPlayer, + handler, prompt, currentMenu, currentPage - ); - + ); + // Close the inventory so player can type in chat clickHandler.player().inventory().close(); - + return true; } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatInputManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatInputManager.java index af6709432d..d2a089729c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatInputManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/GuiChatInputManager.java @@ -37,9 +37,8 @@ import java.util.function.Function; /** - * Manages chat input for GUI interactions. - * This properly cancels chat events so messages don't appear in public chat. - * Also handles re-opening the menu after input is processed. + * Manages chat input for GUI interactions. This properly cancels chat events so messages don't + * appear in public chat. Also handles re-opening the menu after input is processed. * * @author creatorfromhell * @since 6.2.0.11 @@ -47,12 +46,13 @@ public class GuiChatInputManager implements Listener { private static GuiChatInputManager instance; - + private final Map pendingInputs = new ConcurrentHashMap<>(); private final QuickShop plugin; private boolean registered = false; private GuiChatInputManager(@NotNull final QuickShop plugin) { + this.plugin = plugin; } @@ -61,6 +61,7 @@ private GuiChatInputManager(@NotNull final QuickShop plugin) { */ @NotNull public static GuiChatInputManager getInstance() { + if(instance == null) { instance = new GuiChatInputManager(QuickShop.getInstance()); } @@ -68,45 +69,47 @@ public static GuiChatInputManager getInstance() { } /** - * Registers a chat input handler for a player with menu context for re-opening. - * The handler will be called when the player sends a chat message. + * Registers a chat input handler for a player with menu context for re-opening. The handler will + * be called when the player sends a chat message. * * @param player The player to listen for - * @param handler The handler function. Returns true if input is accepted (stop listening), - * false to keep waiting for more input. + * @param handler The handler function. Returns true if input is accepted (stop listening), false + * to keep waiting for more input. * @param prompt The prompt message to send to the player * @param menuName The menu name to re-open after input (e.g., "qs:keeper") * @param menuPage The menu page to re-open */ - public void requestInput(@NotNull final Player player, + public void requestInput(@NotNull final Player player, @NotNull final Function handler, @Nullable final String prompt, @Nullable final String menuName, final int menuPage) { + ensureRegistered(); - + pendingInputs.put(player.getUniqueId(), new ChatInputContext(handler, menuName, menuPage)); - + if(prompt != null && !prompt.isEmpty()) { player.sendMessage(prompt); } - - Log.debug("GuiChatInputManager: Registered input handler for player " + player.getName() + + + Log.debug("GuiChatInputManager: Registered input handler for player " + player.getName() + " (menu: " + menuName + ", page: " + menuPage + ")"); } /** - * Registers a chat input handler for a player without menu context. - * The menu will not be re-opened after input. + * Registers a chat input handler for a player without menu context. The menu will not be + * re-opened after input. * * @param player The player to listen for - * @param handler The handler function. Returns true if input is accepted (stop listening), - * false to keep waiting for more input. + * @param handler The handler function. Returns true if input is accepted (stop listening), false + * to keep waiting for more input. * @param prompt The prompt message to send to the player */ - public void requestInput(@NotNull final Player player, + public void requestInput(@NotNull final Player player, @NotNull final Function handler, @Nullable final String prompt) { + requestInput(player, handler, prompt, null, 1); } @@ -114,9 +117,10 @@ public void requestInput(@NotNull final Player player, * Cancels any pending input request for a player and optionally re-opens the menu. */ public void cancelInput(@NotNull final UUID playerId, final boolean reopenMenu) { + final ChatInputContext context = pendingInputs.remove(playerId); Log.debug("GuiChatInputManager: Cancelled input handler for player " + playerId); - + if(reopenMenu && context != null && context.menuName() != null) { reopenMenu(playerId, context); } @@ -126,6 +130,7 @@ public void cancelInput(@NotNull final UUID playerId, final boolean reopenMenu) * Cancels any pending input request for a player without re-opening menu. */ public void cancelInput(@NotNull final UUID playerId) { + cancelInput(playerId, false); } @@ -133,33 +138,35 @@ public void cancelInput(@NotNull final UUID playerId) { * Checks if a player has a pending input request. */ public boolean hasPendingInput(@NotNull final UUID playerId) { + return pendingInputs.containsKey(playerId); } @EventHandler(priority = EventPriority.LOWEST) public void onChat(final AsyncPlayerChatEvent event) { + final UUID playerId = event.getPlayer().getUniqueId(); final ChatInputContext context = pendingInputs.get(playerId); - + if(context == null) { return; } - + // Cancel the event so it doesn't show in chat event.setCancelled(true); - + final String message = event.getMessage().trim(); final Player eventPlayer = event.getPlayer(); Log.debug("GuiChatInputManager: Received input from " + eventPlayer.getName() + ": " + message); - + // Process on the player's region thread for Folia compatibility - QuickShop.folia().getScheduler().runAtEntityLater(eventPlayer, () -> { + QuickShop.folia().getScheduler().runAtEntityLater(eventPlayer, ()->{ try { final boolean accepted = context.handler().apply(message); if(accepted) { pendingInputs.remove(playerId); Log.debug("GuiChatInputManager: Input accepted, removed handler for " + playerId); - + // Re-open the menu if configured if(context.menuName() != null) { reopenMenu(playerId, context); @@ -168,7 +175,7 @@ public void onChat(final AsyncPlayerChatEvent event) { } catch(final Exception e) { plugin.logger().warn("Error processing GUI chat input for player " + playerId, e); pendingInputs.remove(playerId); - + // Re-open menu even on error if(context.menuName() != null) { reopenMenu(playerId, context); @@ -181,16 +188,17 @@ public void onChat(final AsyncPlayerChatEvent event) { * Re-opens the menu for a player after chat input is processed. */ private void reopenMenu(@NotNull final UUID playerId, @NotNull final ChatInputContext context) { + final Player player = Bukkit.getPlayer(playerId); if(player == null || !player.isOnline()) { return; } - - Log.debug("GuiChatInputManager: Re-opening menu " + context.menuName() + + + Log.debug("GuiChatInputManager: Re-opening menu " + context.menuName() + " page " + context.menuPage() + " for player " + player.getName()); - + // Small delay on player's region thread to ensure everything is cleaned up before re-opening - QuickShop.folia().getScheduler().runAtEntityLater(player, () -> { + QuickShop.folia().getScheduler().runAtEntityLater(player, ()->{ final Player onlinePlayer = Bukkit.getPlayer(playerId); if(onlinePlayer == null || !onlinePlayer.isOnline()) { return; @@ -202,10 +210,12 @@ private void reopenMenu(@NotNull final UUID playerId, @NotNull final ChatInputCo @EventHandler public void onPlayerQuit(final PlayerQuitEvent event) { + pendingInputs.remove(event.getPlayer().getUniqueId()); } private void ensureRegistered() { + if(!registered) { Bukkit.getPluginManager().registerEvents(this, plugin.getJavaPlugin()); registered = true; @@ -217,6 +227,7 @@ private void ensureRegistered() { * Unregisters the listener (call on plugin disable). */ public void shutdown() { + if(registered) { HandlerList.unregisterAll(this); registered = false; @@ -231,5 +242,5 @@ private record ChatInputContext( Function handler, @Nullable String menuName, int menuPage - ) {} + ) { } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java index 7aa358f7f3..2946b7205c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java @@ -105,82 +105,89 @@ public static List getList(final UUID player, final String route, @Nu } /** - * Gets a chat message from gui.yml and formats it as a legacy string. - * This is used for chat messages sent to players from GUI interactions. + * Gets a chat message from gui.yml and formats it as a legacy string. This is used for chat + * messages sent to players from GUI interactions. * * @param path The path under "messages" section in gui.yml (e.g., "keeper.confirm-delete") * @param args Arguments to replace {0}, {1}, etc. placeholders + * * @return The formatted legacy string */ public static String guiMessage(final String path, @Nullable final Object... args) { + String message = QuickShop.getInstance().getGuiConfig().getMessage(path); - + // Replace {0}, {1}, etc. with args if(args != null) { for(int i = 0; i < args.length; i++) { - message = message.replace("{" + i + "}", args[i] != null?args[i].toString() : ""); + message = message.replace("{" + i + "}", args[i] != null? args[i].toString() : ""); } } - + // Parse MiniMessage and convert to legacy final Component component = QuickShop.getInstance().platform().miniMessage().deserialize(message); return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(component); } /** - * Gets an icon's display name from gui.yml as a Component. - * Parses MiniMessage formatting. + * Gets an icon's display name from gui.yml as a Component. Parses MiniMessage formatting. * - * @param config The icon config + * @param config The icon config * @param defaultName The default name if not configured + * * @return The parsed Component */ @NotNull public static Component getConfigDisplay(@Nullable final GuiConfig.IconConfig config, @NotNull final String defaultName) { - final String name = config != null && config.getName() != null?config.getName() : defaultName; + + final String name = config != null && config.getName() != null? config.getName() : defaultName; return QuickShop.getInstance().platform().miniMessage().deserialize(name); } /** - * Gets an icon's display name from gui.yml as a Component with placeholder replacement. - * Parses MiniMessage formatting and replaces {0}, {1}, etc. placeholders. + * Gets an icon's display name from gui.yml as a Component with placeholder replacement. Parses + * MiniMessage formatting and replaces {0}, {1}, etc. placeholders. * - * @param config The icon config + * @param config The icon config * @param defaultName The default name if not configured - * @param args Arguments to replace placeholders + * @param args Arguments to replace placeholders + * * @return The parsed Component */ @NotNull public static Component getConfigDisplay(@Nullable final GuiConfig.IconConfig config, @NotNull final String defaultName, @Nullable final Object... args) { - String name = config != null && config.getName() != null?config.getName() : defaultName; + + String name = config != null && config.getName() != null? config.getName() : defaultName; if(args != null) { for(int i = 0; i < args.length; i++) { - name = name.replace("{" + i + "}", args[i] != null?args[i].toString() : ""); + name = name.replace("{" + i + "}", args[i] != null? args[i].toString() : ""); } } return QuickShop.getInstance().platform().miniMessage().deserialize(name); } /** - * Gets an icon's lore from gui.yml as a list of Components. - * Parses MiniMessage formatting and replaces {0}, {1}, etc. placeholders. + * Gets an icon's lore from gui.yml as a list of Components. Parses MiniMessage formatting and + * replaces {0}, {1}, etc. placeholders. * * @param config The icon config - * @param args Arguments to replace placeholders + * @param args Arguments to replace placeholders + * * @return The parsed list of Components */ @NotNull public static List getConfigLore(@Nullable final GuiConfig.IconConfig config, @Nullable final Object... args) { + final List result = new ArrayList<>(); if(config == null) { return result; } - + for(String line : config.getLore()) { // Replace {0}, {1}, etc. with args if(args != null) { for(int i = 0; i < args.length; i++) { - line = line.replace("{" + i + "}", args[i] != null?args[i].toString() : ""); + line = line.replace("{" + i + "}", args[i] != null? args[i].toString() : ""); } } result.add(QuickShop.getInstance().platform().miniMessage().deserialize(line)); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java index a94130a875..133c764aec 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java @@ -23,12 +23,10 @@ import com.ghostchu.quickshop.menu.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.ClearSearchAction; import com.ghostchu.quickshop.menu.shared.GuiChatAction; -import net.tnemc.menu.core.icon.action.ActionType; -import net.kyori.adventure.text.Component; -import org.bukkit.entity.Player; import net.tnemc.item.providers.SkullProfile; import net.tnemc.menu.core.builder.IconBuilder; import net.tnemc.menu.core.callbacks.page.PageOpenCallback; +import net.tnemc.menu.core.icon.action.ActionType; import net.tnemc.menu.core.icon.action.IconAction; import net.tnemc.menu.core.icon.action.impl.DataAction; import net.tnemc.menu.core.icon.action.impl.RunnableAction; @@ -36,6 +34,7 @@ import net.tnemc.menu.core.viewer.MenuViewer; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; @@ -45,10 +44,8 @@ import static com.ghostchu.quickshop.menu.ShopKeeperMenu.SHOP_DATA_ID; import static com.ghostchu.quickshop.menu.ShopStaffMenu.PLAYER_SEARCH; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.get; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigDisplay; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigLore; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getList; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getShop; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.guiMessage; @@ -82,7 +79,7 @@ public PlayerSelectionPage(final String returnMenu, final String menuName, this.actions = actions; //we need a controller row and then at least one row for items. - this.menuRows = (menuRows <= 1)?2 : menuRows; + this.menuRows = (menuRows <= 1)? 2 : menuRows; } public void handle(final PageOpenCallback callback) { @@ -97,71 +94,71 @@ public void handle(final PageOpenCallback callback) { callback.getPage().getIcons().clear(); final UUID id = viewer.get().uuid(); - + // Load GUI configuration for modern styling final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("staff"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig pageInfoConfig = menuConfig != null?menuConfig.getIcon("page-info") : null; - final GuiConfig.IconConfig backConfig = menuConfig != null?menuConfig.getIcon("back") : null; - final GuiConfig.IconConfig searchConfig = menuConfig != null?menuConfig.getIcon("search") : null; - + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig prevPageConfig = menuConfig != null? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = menuConfig != null? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig pageInfoConfig = menuConfig != null? menuConfig.getIcon("page-info") : null; + final GuiConfig.IconConfig backConfig = menuConfig != null? menuConfig.getIcon("back") : null; + final GuiConfig.IconConfig searchConfig = menuConfig != null? menuConfig.getIcon("search") : null; + // Get search query from viewer data - final String searchQuery = (String) viewer.get().dataOrDefault(PLAYER_SEARCH, ""); - + final String searchQuery = (String)viewer.get().dataOrDefault(PLAYER_SEARCH, ""); + // Filter players by search query final List players = filterPlayers(allPlayers, searchQuery); - + // Set up borders from config (rows 1 and 6 like browse page) - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); - for (final int row : borderRows) { + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(1, 6); + for(final int row : borderRows) { callback.getPage().setRow(row, borderBuilder); } - + // Get list start slot from config (slot 9 = row 2 like browse page) - final int listStartSlot = menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9; - + final int listStartSlot = menuConfig != null? menuConfig.getSection().getInt("list-start-slot", 9) : 9; + final int offset = 9; final int page = (Integer)viewer.get().dataOrDefault(playerPageID, 1); final int items = (menuRows - 2) * offset; // Adjusted for border rows final int start = ((page - 1) * offset); - final int maxPages = (players.size() / items) + (((players.size() % items) > 0)?1 : 0); + final int maxPages = (players.size() / items) + (((players.size() % items) > 0)? 1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // === Control Row (Row 1) === - + // Search button (slot 0) - Left-click to search, Right-click to clear - final String searchMaterial = searchConfig != null?searchConfig.getMaterial() : "ANVIL"; - final int searchSlot = searchConfig != null?searchConfig.getSlot() : 0; - final String currentSearchDisplay = searchQuery.isEmpty()?"None" : searchQuery; - + final String searchMaterial = searchConfig != null? searchConfig.getMaterial() : "ANVIL"; + final int searchSlot = searchConfig != null? searchConfig.getSlot() : 0; + final String currentSearchDisplay = searchQuery.isEmpty()? "None" : searchQuery; + // Capture variables for closure final Long capturedShopId = shop.get().getShopId(); - + callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(searchMaterial, 1) .display(getConfigDisplay(searchConfig, "Search: {0}", currentSearchDisplay)) .lore(getConfigLore(searchConfig, currentSearchDisplay))) .withSlot(searchSlot) - .withActions(new GuiChatAction((message) -> { + .withActions(new GuiChatAction((message)->{ // Handle clear command - final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))?"" : message; - + final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))? "" : message; + // Create new viewer with state preserved + new search value final net.tnemc.menu.core.viewer.MenuViewer newViewer = new net.tnemc.menu.core.viewer.MenuViewer(id); newViewer.addData(SHOP_DATA_ID, capturedShopId); // Use shop ID like other menus newViewer.addData(PLAYER_SEARCH, searchValue); newViewer.addData(playerPageID, 1); // Reset to page 1 on new search net.tnemc.menu.core.manager.MenuManager.instance().addViewer(newViewer); - + // Reopen the menu at the add staff page final Player p = Bukkit.getPlayer(id); - if (p != null && p.isOnline()) { + if(p != null && p.isOnline()) { final net.tnemc.menu.core.compatibility.MenuPlayer menuPlayerObj = QuickShop.getInstance().createMenuPlayer(p); menuPlayerObj.inventory().openMenu(menuPlayerObj, menuName, menuPage); } @@ -171,21 +168,21 @@ public void handle(final PageOpenCallback callback) { .build()); // Back button (slot 8 - right side like browse close button) - final String backMaterial = backConfig != null?backConfig.getMaterial() : "OAK_DOOR"; - final int backSlot = backConfig != null?backConfig.getSlot() : 8; + final String backMaterial = backConfig != null? backConfig.getMaterial() : "OAK_DOOR"; + final int backSlot = backConfig != null? backConfig.getSlot() : 8; callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(backMaterial, 1) .display(getConfigDisplay(backConfig, "Back to Staff List"))) .withActions(new SwitchPageAction(returnMenu, returnPage)) .withSlot(backSlot) .build()); - + // === Pagination Row (Bottom - Row 6) === - final String prevMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevSlot = prevPageConfig != null?prevPageConfig.getSlot() : 48; - final String nextMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextSlot = nextPageConfig != null?nextPageConfig.getSlot() : 50; - final String pageInfoMaterial = pageInfoConfig != null?pageInfoConfig.getMaterial() : "BOOK"; - final int pageInfoSlot = pageInfoConfig != null?pageInfoConfig.getSlot() : 49; + final String prevMaterial = prevPageConfig != null? prevPageConfig.getMaterial() : "ARROW"; + final int prevSlot = prevPageConfig != null? prevPageConfig.getSlot() : 48; + final String nextMaterial = nextPageConfig != null? nextPageConfig.getMaterial() : "ARROW"; + final int nextSlot = nextPageConfig != null? nextPageConfig.getSlot() : 50; + final String pageInfoMaterial = pageInfoConfig != null? pageInfoConfig.getMaterial() : "BOOK"; + final int pageInfoSlot = pageInfoConfig != null? pageInfoConfig.getSlot() : 49; if(maxPages > 1) { callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(prevMaterial, 1) @@ -230,7 +227,7 @@ public void handle(final PageOpenCallback callback) { } catch(final Exception ignore) { } - final String name = (player.getName() != null)?player.getName() : uuid.toString(); + final String name = (player.getName() != null)? player.getName() : uuid.toString(); callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of("PLAYER_HEAD", 1) .display(QuickShop.getInstance().platform().miniMessage().deserialize("" + name + "")) .lore(getConfigLore(null, name)) @@ -265,23 +262,26 @@ public List sorted(final Shop shop) { } return sortedPlayers; } - + /** * Filter players by search query (player name) - * @param players List of players to filter + * + * @param players List of players to filter * @param searchQuery Search query to filter by + * * @return Filtered list of players */ private List filterPlayers(final List players, final String searchQuery) { - if (searchQuery == null || searchQuery.trim().isEmpty()) { + + if(searchQuery == null || searchQuery.trim().isEmpty()) { return players; } - + final String query = searchQuery.toLowerCase(Locale.ROOT).trim(); - + return players.stream() - .filter(player -> { - if (player.getName() != null) { + .filter(player->{ + if(player.getName() != null) { return player.getName().toLowerCase(Locale.ROOT).contains(query); } // Also match UUID if name is not available diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java index aa939a3112..c3615d841e 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java @@ -47,10 +47,8 @@ import static com.ghostchu.quickshop.menu.ShopKeeperMenu.SHOP_DATA_ID; import static com.ghostchu.quickshop.menu.ShopStaffMenu.STAFF_ADD; import static com.ghostchu.quickshop.menu.ShopStaffMenu.STAFF_SEARCH; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.get; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigDisplay; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getConfigLore; -import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getList; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.getShop; import static com.ghostchu.quickshop.menu.shared.QuickShopPage.guiMessage; @@ -84,7 +82,7 @@ public StaffSelectionPage(final String returnMenu, final String menuName, this.actions = actions; //we need a controller row and then at least one row for items. - this.menuRows = (menuRows <= 1)?2 : menuRows; + this.menuRows = (menuRows <= 1)? 2 : menuRows; } public void handle(final PageOpenCallback callback) { @@ -102,72 +100,72 @@ public void handle(final PageOpenCallback callback) { final UUID id = viewer.get().uuid(); final Player viewerPlayer = Bukkit.getPlayer(id); if(viewerPlayer != null) { - + // Load GUI configuration for modern styling final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("staff"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig prevPageConfig = menuConfig != null?menuConfig.getIcon("previous-page") : null; - final GuiConfig.IconConfig nextPageConfig = menuConfig != null?menuConfig.getIcon("next-page") : null; - final GuiConfig.IconConfig pageInfoConfig = menuConfig != null?menuConfig.getIcon("page-info") : null; - final GuiConfig.IconConfig addStaffConfig = menuConfig != null?menuConfig.getIcon("add-staff") : null; - final GuiConfig.IconConfig backConfig = menuConfig != null?menuConfig.getIcon("back") : null; - final GuiConfig.IconConfig searchConfig = menuConfig != null?menuConfig.getIcon("search") : null; - + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig prevPageConfig = menuConfig != null? menuConfig.getIcon("previous-page") : null; + final GuiConfig.IconConfig nextPageConfig = menuConfig != null? menuConfig.getIcon("next-page") : null; + final GuiConfig.IconConfig pageInfoConfig = menuConfig != null? menuConfig.getIcon("page-info") : null; + final GuiConfig.IconConfig addStaffConfig = menuConfig != null? menuConfig.getIcon("add-staff") : null; + final GuiConfig.IconConfig backConfig = menuConfig != null? menuConfig.getIcon("back") : null; + final GuiConfig.IconConfig searchConfig = menuConfig != null? menuConfig.getIcon("search") : null; + // Get search query from viewer data - final String searchQuery = (String) viewer.get().dataOrDefault(STAFF_SEARCH, ""); - + final String searchQuery = (String)viewer.get().dataOrDefault(STAFF_SEARCH, ""); + // Filter staffs by search query final List staffs = filterStaffs(allStaffs, searchQuery); - + // Set up borders from config (rows 1 and 6 like browse page) - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 6); - for (final int row : borderRows) { + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(1, 6); + for(final int row : borderRows) { callback.getPage().setRow(row, borderBuilder); } - + // Get list start slot from config (slot 9 = row 2 like browse page) - final int listStartSlot = menuConfig != null?menuConfig.getSection().getInt("list-start-slot", 9) : 9; - + final int listStartSlot = menuConfig != null? menuConfig.getSection().getInt("list-start-slot", 9) : 9; + final int offset = 9; final int page = (Integer)viewer.get().dataOrDefault(staffPageID, 1); final int items = (menuRows - 2) * offset; // Adjusted for border rows final int start = ((page - 1) * offset); - final int maxPages = (staffs.size() / items) + (((staffs.size() % items) > 0)?1 : 0); + final int maxPages = (staffs.size() / items) + (((staffs.size() % items) > 0)? 1 : 0); - final int prev = (page <= 1)?maxPages : page - 1; - final int next = (page >= maxPages)?1 : page + 1; + final int prev = (page <= 1)? maxPages : page - 1; + final int next = (page >= maxPages)? 1 : page + 1; // === Control Row (Row 1) === - + // Search button (slot 0) - Left-click to search, Right-click to clear - final String searchMaterial = searchConfig != null?searchConfig.getMaterial() : "ANVIL"; - final int searchSlot = searchConfig != null?searchConfig.getSlot() : 0; - final String currentSearchDisplay = searchQuery.isEmpty()?"None" : searchQuery; - + final String searchMaterial = searchConfig != null? searchConfig.getMaterial() : "ANVIL"; + final int searchSlot = searchConfig != null? searchConfig.getSlot() : 0; + final String currentSearchDisplay = searchQuery.isEmpty()? "None" : searchQuery; + // Capture variables for closure final Long capturedShopId = shop.get().getShopId(); - + callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(searchMaterial, 1) .display(getConfigDisplay(searchConfig, "Search: {0}", currentSearchDisplay)) .lore(getConfigLore(searchConfig, currentSearchDisplay))) .withSlot(searchSlot) - .withActions(new GuiChatAction((message) -> { + .withActions(new GuiChatAction((message)->{ // Handle clear command - final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))?"" : message; - + final String searchValue = (message.equalsIgnoreCase("clear") || message.equals("0"))? "" : message; + // Create new viewer with state preserved + new search value final net.tnemc.menu.core.viewer.MenuViewer newViewer = new net.tnemc.menu.core.viewer.MenuViewer(id); newViewer.addData(SHOP_DATA_ID, capturedShopId); // Use shop ID like other menus newViewer.addData(STAFF_SEARCH, searchValue); newViewer.addData(staffPageID, 1); // Reset to page 1 on new search net.tnemc.menu.core.manager.MenuManager.instance().addViewer(newViewer); - + // Reopen the menu final Player p = Bukkit.getPlayer(id); - if (p != null && p.isOnline()) { + if(p != null && p.isOnline()) { final net.tnemc.menu.core.compatibility.MenuPlayer menuPlayer = QuickShop.getInstance().createMenuPlayer(p); menuPlayer.inventory().openMenu(menuPlayer, menuName, menuPage); } @@ -177,8 +175,8 @@ public void handle(final PageOpenCallback callback) { .build()); // Add staff button (slot 4 - center) - final String addStaffMaterial = addStaffConfig != null?addStaffConfig.getMaterial() : "EMERALD"; - final int addStaffSlot = addStaffConfig != null?addStaffConfig.getSlot() : 4; + final String addStaffMaterial = addStaffConfig != null? addStaffConfig.getMaterial() : "EMERALD"; + final int addStaffSlot = addStaffConfig != null? addStaffConfig.getSlot() : 4; callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(addStaffMaterial, 1) .display(getConfigDisplay(addStaffConfig, "Add Staff Member")) .lore(getConfigLore(addStaffConfig))) @@ -187,21 +185,21 @@ public void handle(final PageOpenCallback callback) { .build()); // Back button (slot 8 - right side like browse close button) - final String backMaterial = backConfig != null?backConfig.getMaterial() : "OAK_DOOR"; - final int backSlot = backConfig != null?backConfig.getSlot() : 8; + final String backMaterial = backConfig != null? backConfig.getMaterial() : "OAK_DOOR"; + final int backSlot = backConfig != null? backConfig.getSlot() : 8; callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(backMaterial, 1) .display(getConfigDisplay(backConfig, "Back to Shop"))) .withActions(new SwitchPageAction(returnMenu, returnPage)) .withSlot(backSlot) .build()); - + // === Pagination Row (Bottom - Row 6) === - final String prevMaterial = prevPageConfig != null?prevPageConfig.getMaterial() : "ARROW"; - final int prevSlot = prevPageConfig != null?prevPageConfig.getSlot() : 48; - final String nextMaterial = nextPageConfig != null?nextPageConfig.getMaterial() : "ARROW"; - final int nextSlot = nextPageConfig != null?nextPageConfig.getSlot() : 50; - final String pageInfoMaterial = pageInfoConfig != null?pageInfoConfig.getMaterial() : "BOOK"; - final int pageInfoSlot = pageInfoConfig != null?pageInfoConfig.getSlot() : 49; + final String prevMaterial = prevPageConfig != null? prevPageConfig.getMaterial() : "ARROW"; + final int prevSlot = prevPageConfig != null? prevPageConfig.getSlot() : 48; + final String nextMaterial = nextPageConfig != null? nextPageConfig.getMaterial() : "ARROW"; + final int nextSlot = nextPageConfig != null? nextPageConfig.getSlot() : 50; + final String pageInfoMaterial = pageInfoConfig != null? pageInfoConfig.getMaterial() : "BOOK"; + final int pageInfoSlot = pageInfoConfig != null? pageInfoConfig.getSlot() : 49; if(maxPages > 1) { callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(prevMaterial, 1) @@ -247,7 +245,7 @@ public void handle(final PageOpenCallback callback) { } catch(final Exception ignore) { } - final String name = (player.isPresent() && player.get().getName() != null)?player.get().getName() : uuid.toString(); + final String name = (player.isPresent() && player.get().getName() != null)? player.get().getName() : uuid.toString(); callback.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of("PLAYER_HEAD", 1) .display(QuickShop.getInstance().platform().miniMessage().deserialize("" + name + "")) .lore(getConfigLore(null, name)) @@ -288,24 +286,27 @@ public void handle(final PageOpenCallback callback) { } } } - + /** * Filter staff UUIDs by search query (player name) - * @param staffs List of staff UUIDs + * + * @param staffs List of staff UUIDs * @param searchQuery Search query to filter by + * * @return Filtered list of staff UUIDs */ private List filterStaffs(final List staffs, final String searchQuery) { - if (searchQuery == null || searchQuery.trim().isEmpty()) { + + if(searchQuery == null || searchQuery.trim().isEmpty()) { return staffs; } - + final String query = searchQuery.toLowerCase(Locale.ROOT).trim(); - + return staffs.stream() - .filter(uuid -> { + .filter(uuid->{ final Optional player = QuickShopPage.getPlayer(uuid); - if (player.isPresent() && player.get().getName() != null) { + if(player.isPresent() && player.get().getName() != null) { return player.get().getName().toLowerCase(Locale.ROOT).contains(query); } // Also match UUID if name is not available diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java index 68446a4144..65336da688 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java @@ -30,7 +30,6 @@ import com.ghostchu.quickshop.shop.SimpleInfo; import com.ghostchu.quickshop.shop.inventory.BukkitInventoryWrapper; import com.ghostchu.quickshop.util.Util; -import net.kyori.adventure.text.Component; import net.tnemc.item.bukkit.BukkitItemStack; import net.tnemc.item.providers.SkullProfile; import net.tnemc.menu.core.builder.IconBuilder; @@ -75,60 +74,60 @@ public void handle(final PageOpenCallback open) { if(shop.isPresent() && player != null) { final EconomyProvider eco = QuickShop.getInstance().getEconomyManager().provider(); - + // Load GUI configuration final GuiConfig.MenuConfig menuConfig = QuickShop.getInstance().getGuiConfig().getMenuConfig("trade"); - final GuiConfig.IconConfig borderConfig = menuConfig != null?menuConfig.getIcon("border") : null; - final GuiConfig.IconConfig customAmountConfig = menuConfig != null?menuConfig.getIcon("custom-amount") : null; - final GuiConfig.IconConfig quantityConfig = menuConfig != null?menuConfig.getIcon("quantity-buttons") : null; - final GuiConfig.IconConfig shopItemConfig = menuConfig != null?menuConfig.getIcon("shop-item") : null; - final GuiConfig.IconConfig infoStockConfig = menuConfig != null?menuConfig.getIcon("info-stock") : null; - final GuiConfig.IconConfig infoPriceConfig = menuConfig != null?menuConfig.getIcon("info-price") : null; - final GuiConfig.IconConfig infoSellerConfig = menuConfig != null?menuConfig.getIcon("info-seller") : null; - final GuiConfig.IconConfig closeConfig = menuConfig != null?menuConfig.getIcon("close") : null; + final GuiConfig.IconConfig borderConfig = menuConfig != null? menuConfig.getIcon("border") : null; + final GuiConfig.IconConfig customAmountConfig = menuConfig != null? menuConfig.getIcon("custom-amount") : null; + final GuiConfig.IconConfig quantityConfig = menuConfig != null? menuConfig.getIcon("quantity-buttons") : null; + final GuiConfig.IconConfig shopItemConfig = menuConfig != null? menuConfig.getIcon("shop-item") : null; + final GuiConfig.IconConfig infoStockConfig = menuConfig != null? menuConfig.getIcon("info-stock") : null; + final GuiConfig.IconConfig infoPriceConfig = menuConfig != null? menuConfig.getIcon("info-price") : null; + final GuiConfig.IconConfig infoSellerConfig = menuConfig != null? menuConfig.getIcon("info-seller") : null; + final GuiConfig.IconConfig closeConfig = menuConfig != null? menuConfig.getIcon("close") : null; // Set up our borders from config (gray for modern look) - final String borderMaterial = borderConfig != null?borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; + final String borderMaterial = borderConfig != null? borderConfig.getMaterial() : "GRAY_STAINED_GLASS_PANE"; final IconBuilder borderBuilder = new IconBuilder(QuickShop.getInstance().stack().of(borderMaterial, 1)); - + // Get border rows from config or use defaults (rows 1, 4, 6 for modern 6-row layout) - final List borderRows = borderConfig != null?borderConfig.getRows() : List.of(1, 4, 6); - for (final int row : borderRows) { + final List borderRows = borderConfig != null? borderConfig.getRows() : List.of(1, 4, 6); + for(final int row : borderRows) { open.getPage().setRow(row, borderBuilder); } final ItemStack shopItem = shop.get().getItem(); final int amount = shopItem.getAmount(); // Use cache to avoid Folia cross-region block access issues - final int stock = (shop.get().isBuying())?-1 : MarketUtils.getStockFromCache(shop.get()); - final String stockString = (shop.get().isUnlimited())?"Unlimited" : stock + ""; + final int stock = (shop.get().isBuying())? -1 : MarketUtils.getStockFromCache(shop.get()); + final String stockString = (shop.get().isUnlimited())? "Unlimited" : stock + ""; final String priceFormatted = eco.format(BigDecimal.valueOf(shop.get().getPrice()), - shop.get().getLocation().getWorld().getName(), - shop.get().getCurrency()); + shop.get().getLocation().getWorld().getName(), + shop.get().getCurrency()); // Shop item display slot from config (centered in row 2) - final int shopItemSlot = shopItemConfig != null?shopItemConfig.getSlot() : 13; + final int shopItemSlot = shopItemConfig != null? shopItemConfig.getSlot() : 13; open.getPage().addIcon(new IconBuilder(new BukkitItemStack().of(shopItem)).withSlot(shopItemSlot).build()); // Info icons row (row 3) - Stock info - final String infoStockMaterial = infoStockConfig != null?infoStockConfig.getMaterial() : "CHEST"; - final int infoStockSlot = infoStockConfig != null?infoStockConfig.getSlot() : 21; + final String infoStockMaterial = infoStockConfig != null? infoStockConfig.getMaterial() : "CHEST"; + final int infoStockSlot = infoStockConfig != null? infoStockConfig.getSlot() : 21; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(infoStockMaterial, 1) - .display(getConfigDisplay(infoStockConfig, "Stock Information")) - .lore(getConfigLore(infoStockConfig, stockString))) - .withSlot(infoStockSlot).build()); + .display(getConfigDisplay(infoStockConfig, "Stock Information")) + .lore(getConfigLore(infoStockConfig, stockString))) + .withSlot(infoStockSlot).build()); // Info icons row - Price info - final String infoPriceMaterial = infoPriceConfig != null?infoPriceConfig.getMaterial() : "GOLD_INGOT"; - final int infoPriceSlot = infoPriceConfig != null?infoPriceConfig.getSlot() : 22; + final String infoPriceMaterial = infoPriceConfig != null? infoPriceConfig.getMaterial() : "GOLD_INGOT"; + final int infoPriceSlot = infoPriceConfig != null? infoPriceConfig.getSlot() : 22; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(infoPriceMaterial, 1) - .display(getConfigDisplay(infoPriceConfig, "Price Information")) - .lore(getConfigLore(infoPriceConfig, priceFormatted, amount))) - .withSlot(infoPriceSlot).build()); + .display(getConfigDisplay(infoPriceConfig, "Price Information")) + .lore(getConfigLore(infoPriceConfig, priceFormatted, amount))) + .withSlot(infoPriceSlot).build()); // Info icons row - Seller info - final String infoSellerMaterial = infoSellerConfig != null?infoSellerConfig.getMaterial() : "PLAYER_HEAD"; - final int infoSellerSlot = infoSellerConfig != null?infoSellerConfig.getSlot() : 23; + final String infoSellerMaterial = infoSellerConfig != null? infoSellerConfig.getMaterial() : "PLAYER_HEAD"; + final int infoSellerSlot = infoSellerConfig != null? infoSellerConfig.getSlot() : 23; SkullProfile sellerProfile = null; if(shop.get().getOwner().isRealPlayer()) { sellerProfile = new SkullProfile(); @@ -136,16 +135,16 @@ public void handle(final PageOpenCallback open) { } final String ownerName = shop.get().getOwner().getDisplay(); open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(infoSellerMaterial, 1) - .display(getConfigDisplay(infoSellerConfig, "Seller Information")) - .lore(getConfigLore(infoSellerConfig, ownerName)) - .profile(sellerProfile)) - .withSlot(infoSellerSlot).build()); + .display(getConfigDisplay(infoSellerConfig, "Seller Information")) + .lore(getConfigLore(infoSellerConfig, ownerName)) + .profile(sellerProfile)) + .withSlot(infoSellerSlot).build()); // Custom amount button from config (NAME_TAG for intuitive "enter amount") - final String customAmountMaterial = customAmountConfig != null?customAmountConfig.getMaterial() : "NAME_TAG"; - final int customAmountSlot = customAmountConfig != null?customAmountConfig.getSlot() : 43; - - final String enterPath = (shop.get().isSelling())?"trade.enter-buy" : "trade.enter-sell"; + final String customAmountMaterial = customAmountConfig != null? customAmountConfig.getMaterial() : "NAME_TAG"; + final int customAmountSlot = customAmountConfig != null? customAmountConfig.getSlot() : 43; + + final String enterPath = (shop.get().isSelling())? "trade.enter-buy" : "trade.enter-sell"; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(customAmountMaterial, 1) .display(getConfigDisplay(customAmountConfig, "Custom Order")) .lore(getConfigLore(customAmountConfig, amount, stockString))) @@ -183,14 +182,14 @@ public void handle(final PageOpenCallback open) { .withSlot(customAmountSlot).build()); // Quantity buttons from config - color-coded (LIME_CONCRETE for buy, ORANGE_CONCRETE for sell) - final String quantityMaterialBuy = quantityConfig != null && quantityConfig.getSection() != null - ?quantityConfig.getSection().getString("material-buy", "LIME_CONCRETE") : "LIME_CONCRETE"; - final String quantityMaterialSell = quantityConfig != null && quantityConfig.getSection() != null - ?quantityConfig.getSection().getString("material-sell", "ORANGE_CONCRETE") : "ORANGE_CONCRETE"; - final String quantityMaterial = shop.get().isSelling()?quantityMaterialBuy : quantityMaterialSell; - - final List configQuantities = quantityConfig != null?quantityConfig.getQuantities() : List.of(1, 2, 4, 8, 16, 64); - final List configSlots = quantityConfig != null?quantityConfig.getSlots() : List.of(37, 38, 39, 40, 41, 42); + final String quantityMaterialBuy = quantityConfig != null && quantityConfig.section() != null + ? quantityConfig.section().getString("material-buy", "LIME_CONCRETE") : "LIME_CONCRETE"; + final String quantityMaterialSell = quantityConfig != null && quantityConfig.section() != null + ? quantityConfig.section().getString("material-sell", "ORANGE_CONCRETE") : "ORANGE_CONCRETE"; + final String quantityMaterial = shop.get().isSelling()? quantityMaterialBuy : quantityMaterialSell; + + final List configQuantities = quantityConfig != null? quantityConfig.getQuantities() : List.of(1, 2, 4, 8, 16, 64); + final List configSlots = quantityConfig != null? quantityConfig.getSlots() : List.of(37, 38, 39, 40, 41, 42); for(int i = 0; i < configQuantities.size() && i < configSlots.size(); i++) { @@ -200,7 +199,7 @@ public void handle(final PageOpenCallback open) { final String totalPrice = eco.format(BigDecimal.valueOf((quantity * shop.get().getPrice())), shop.get().getLocation().getWorld().getName(), shop.get().getCurrency()); - final String displayText = (shop.get().isSelling())?"Buy x" + adjustedAmount + "" : "Sell x" + adjustedAmount + ""; + final String displayText = (shop.get().isSelling())? "Buy x" + adjustedAmount + "" : "Sell x" + adjustedAmount + ""; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(quantityMaterial, Math.min(adjustedAmount, 64)) .display(QuickShop.getInstance().platform().miniMessage().deserialize(displayText)) @@ -223,12 +222,12 @@ public void handle(final PageOpenCallback open) { } // Close button - centered at bottom (slot 49) - final String closeMaterial = closeConfig != null?closeConfig.getMaterial() : "BARRIER"; - final int closeSlot = closeConfig != null?closeConfig.getSlot() : 49; + final String closeMaterial = closeConfig != null? closeConfig.getMaterial() : "BARRIER"; + final int closeSlot = closeConfig != null? closeConfig.getSlot() : 49; open.getPage().addIcon(new IconBuilder(QuickShop.getInstance().stack().of(closeMaterial, 1) - .display(getConfigDisplay(closeConfig, "Close"))) - .withActions(new RunnableAction((click -> viewer.get().close(QuickShop.getInstance().createMenuPlayer(player))))) - .withSlot(closeSlot).build()); + .display(getConfigDisplay(closeConfig, "Close"))) + .withActions(new RunnableAction((click->viewer.get().close(QuickShop.getInstance().createMenuPlayer(player))))) + .withSlot(closeSlot).build()); } } } From 1f7bc6dc000a15845b421cc1d3abf73cf5116e8f Mon Sep 17 00:00:00 2001 From: YuanYuanOwO Date: Tue, 30 Dec 2025 14:13:30 +0800 Subject: [PATCH 09/19] add eu maven repo --- .../quickshop/common/util/mirror/MavenCentralMirror.java | 1 + 1 file changed, 1 insertion(+) diff --git a/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java b/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java index 946091cbdf..2488dd2799 100644 --- a/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java +++ b/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java @@ -3,6 +3,7 @@ public enum MavenCentralMirror { CENTRAL("US", "https://repo1.maven.org/maven2", "https://repo1.maven.org/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), APACHE("US", "https://repo.maven.apache.org/maven2", "https://repo.maven.apache.org/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), + GOOGLE("EU", "https://maven-central-eu.storage-download.googleapis.com", "https://maven-central-eu.storage-download.googleapis.com/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"); ALIYUN("CN", "https://maven.aliyun.com/repository/public", "https://maven.aliyun.com/repository/central/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), TENCENT("CN", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), HUAWEI("CN", "https://repo.huaweicloud.com/repository/maven", "https://repo.huaweicloud.com/repository/maven/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"); From 0c44325eeacaa760641b6bed67275a6f37d348cf Mon Sep 17 00:00:00 2001 From: YuanYuanOwO Date: Tue, 30 Dec 2025 14:28:30 +0800 Subject: [PATCH 10/19] fix build and cleanup plugin.yml --- .../src/main/resources/plugin.yml | 30 ++----------------- .../util/mirror/MavenCentralMirror.java | 2 +- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/quickshop-bukkit/src/main/resources/plugin.yml b/quickshop-bukkit/src/main/resources/plugin.yml index 6797583530..b619cc0065 100644 --- a/quickshop-bukkit/src/main/resources/plugin.yml +++ b/quickshop-bukkit/src/main/resources/plugin.yml @@ -25,42 +25,18 @@ api-version: '1.20' # TODO: https://github.com/PaperMC/Paper/pull/7177 #libraries: # - org.apache.commons:commons-compress:1.25.0 -# - org.apache.commons:commons-lang3:3.14.0 -# - org.apache.commons:commons-text:1.11.0 +# - org.apache.commons:commons-lang3:3.18.0 # - com.rollbar:rollbar-java:1.9.0 # - commons-codec:commons-codec:1.15 -# - cc.carm.lib:easysql-hikaricp:0.4.7 # - com.h2database:h2:2.1.214 -# - com.ghostchu:simplereloadlib:1.1.2 # - com.konghq:unirest-java:3.14.5 -# - com.github.juliomarcopineda:jdbc-stream:0.1.1 -# - one.util:streamex:0.8.2 # - commons-lang:commons-lang:2.6 -# - net.sourceforge.csvjdbc:csvjdbc:1.0.41 +# - net.sourceforge.csvjdbc:csvjdbc:1.0.42 # - org.dom4j:dom4j:2.1.4 -# - org.slf4j:slf4j-jdk14:2.0.10 +# - com.google.guava:guava:33.1.0-jre # - com.vdurmont:semver4j:3.1.0 # - com.ghostchu.crowdin:crowdinota:1.0.3 # - com.google.code.gson:gson:2.10.1 -# - net.kyori:adventure-api:4.15.0 -# - net.kyori:adventure-key:4.15.0 -# - net.kyori:adventure-nbt:4.15.0 -# - net.kyori:adventure-platform-api:4.3.2 -# - net.kyori:adventure-platform-bukkit:4.3.2 -# - net.kyori:adventure-platform-facet:4.3.2 -# - net.kyori:adventure-platform-viaversion:4.3.2 -# - net.kyori:adventure-serializer-configurate4:4.15.0 -# - net.kyori:adventure-text-logger-slf4j:4.15.0 -# - net.kyori:adventure-text-minimessage:4.15.0 -# - net.kyori:adventure-text-serializer-ansi:4.15.0 -# - net.kyori:adventure-text-serializer-bungeecord:4.3.2 -# - net.kyori:adventure-text-serializer-gson-legacy-impl:4.15.0 -# - net.kyori:adventure-text-serializer-json:4.15.0 -# - net.kyori:adventure-text-serializer-json-legacy-impl:4.15.0 -# - net.kyori:adventure-text-serializer-legacy:4.15.0 -# - net.kyori:adventure-text-serializer-plain:4.15.0 -# - net.kyori:examination-api:1.3.0 -# - net.kyori:examination-string:1.3.0 authors: - Ghost_chu - PotatoCraft Studio diff --git a/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java b/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java index 2488dd2799..4018456308 100644 --- a/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java +++ b/quickshop-common/src/main/java/com/ghostchu/quickshop/common/util/mirror/MavenCentralMirror.java @@ -3,7 +3,7 @@ public enum MavenCentralMirror { CENTRAL("US", "https://repo1.maven.org/maven2", "https://repo1.maven.org/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), APACHE("US", "https://repo.maven.apache.org/maven2", "https://repo.maven.apache.org/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), - GOOGLE("EU", "https://maven-central-eu.storage-download.googleapis.com", "https://maven-central-eu.storage-download.googleapis.com/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"); + GOOGLE("EU", "https://maven-central-eu.storage-download.googleapis.com", "https://maven-central-eu.storage-download.googleapis.com/maven2/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), ALIYUN("CN", "https://maven.aliyun.com/repository/public", "https://maven.aliyun.com/repository/central/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), TENCENT("CN", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"), HUAWEI("CN", "https://repo.huaweicloud.com/repository/maven", "https://repo.huaweicloud.com/repository/maven/net/kyori/adventure-api/4.9.1/adventure-api-4.9.1.pom"); From c81bc7d63722f249f139bca9ab065ffd8ca2cb0a Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Thu, 1 Jan 2026 21:18:07 -0500 Subject: [PATCH 11/19] Remove legacy configuration handling: delete `ConfigProvider`, `ConfigUpdateScript`, and related utilities, refactor to use `YamlDocument` and `MainConfig` for streamlined configuration management. --- .../quickshop/addon/displaycontrol/Main.java | 2 +- pom.xml | 10 +- quickshop-api/pom.xml | 24 ++ .../quickshop/api/config/QSConfig.java | 134 +++++++++ quickshop-bukkit/pom.xml | 6 + .../com/ghostchu/quickshop/QuickShop.java | 34 ++- .../command/subcommand/SubCommand_Name.java | 2 +- .../quickshop/database/HikariUtil.java | 9 +- .../localization/text/SimpleTextManager.java | 2 +- .../menu/config/InteractionConfig.java | 53 ++++ .../quickshop/menu/config/MainConfig.java | 53 ++++ .../ghostchu/quickshop/papi/PAPICache.java | 6 +- .../QuickShopInteractionManager.java | 17 +- .../quickshop/util/ChatSheetPrinter.java | 2 +- .../quickshop/util/config/ConfigProvider.java | 92 ------ .../util/config/ConfigUpdateScript.java | 273 ------------------ .../util/config/ConfigurationFixer.java | 66 ----- .../util/config/ConfigurationUpdater.java | 131 --------- .../quickshop/util/config/UpdateScript.java | 24 -- .../item/QuickShopItemMatcherImpl.java | 7 +- .../paste/item/ChatProcessorInfoItem.java | 2 - .../util/privacy/PrivacyController.java | 7 +- .../quickshop/watcher/UpdateWatcher.java | 1 - .../src/main/resources/interaction.yml | 6 +- quickshop-common/pom.xml | 46 +++ 25 files changed, 372 insertions(+), 637 deletions(-) create mode 100644 quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java create mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java create mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigProvider.java delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigUpdateScript.java delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationFixer.java delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationUpdater.java delete mode 100644 quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/UpdateScript.java diff --git a/addon/displaycontrol/src/main/java/com/ghostchu/quickshop/addon/displaycontrol/Main.java b/addon/displaycontrol/src/main/java/com/ghostchu/quickshop/addon/displaycontrol/Main.java index 016f90969e..16154a22b8 100644 --- a/addon/displaycontrol/src/main/java/com/ghostchu/quickshop/addon/displaycontrol/Main.java +++ b/addon/displaycontrol/src/main/java/com/ghostchu/quickshop/addon/displaycontrol/Main.java @@ -122,7 +122,7 @@ public DisplayControlDatabaseHelper getDatabaseHelper() { } @Override - public void onPluginMessageReceived(@NotNull final String channel, @NotNull final Player player, final byte @NotNull [] message) { + public void onPluginMessageReceived(@NotNull final String channel, @NotNull final Player player, final @NotNull byte[] message) { if(!channel.equalsIgnoreCase(BUNGEE_CHANNEL)) { return; diff --git a/pom.xml b/pom.xml index e9b2432ff8..f064fd6c3d 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ 4.3.4 4.3.4 4.3.4 + 1.3.7 3.18.0 0.4.7 1.3.0 @@ -213,6 +214,7 @@ io.vertx:vertx-core:* io.vertx:vertx-web:* org.eclipse.aether:*:* + dev.dejvokep:boosted-yaml org.slf4j:slf4j-jdk14:* cc.carm.lib:* com.rollbar:* @@ -427,12 +429,6 @@ 1.18.38 provided - - org.jetbrains - annotations - 26.0.2 - provided - org.slf4j @@ -457,6 +453,8 @@ org.jetbrains annotations + 26.0.2-1 + provided diff --git a/quickshop-api/pom.xml b/quickshop-api/pom.xml index 8e4a53a0b1..4d99ea84bb 100644 --- a/quickshop-api/pom.xml +++ b/quickshop-api/pom.xml @@ -13,6 +13,12 @@ takari-jar + + org.jetbrains + annotations + 26.0.2-1 + provided + com.ghostchu quickshop-common @@ -23,6 +29,24 @@ io.papermc.paper paper-api ${depend.paper} + + + org.jetbrains + annotations + + + + + dev.dejvokep + boosted-yaml + 1.3.7 + compile + + + org.jetbrains + annotations-java5 + + \ No newline at end of file diff --git a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java new file mode 100644 index 0000000000..d9ca170115 --- /dev/null +++ b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java @@ -0,0 +1,134 @@ +package com.ghostchu.quickshop.api.config; + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import com.ghostchu.quickshop.api.QuickShopAPI; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.settings.Settings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * QSConfig + * + * @author creatorfromhell + * @since 6.2.0.11 + */ +public class QSConfig { + + protected final String fileName; + protected final File file; + protected final String defaults; + protected final List nodes = new ArrayList<>(); + protected YamlDocument yaml; + protected Settings[] settings; + + public QSConfig(final String fileName, final String defaults, final List nodes, final Settings... settings) { + + this.defaults = defaults; + this.fileName = fileName; + this.nodes.addAll(nodes); + file = new File(QuickShopAPI.getPluginInstance().getDataFolder(), fileName); + + this.settings = settings; + + if(!file.exists()) { + QuickShopAPI.getPluginInstance().getLogger().warning("Configuration doesn't exist! File Name:" + fileName); + } + } + + public boolean load() { + + try(final InputStream in = getResource(defaults)) { + + if(in != null) { + this.yaml = YamlDocument.create(file, in, settings); + + return true; + } + } catch(final IOException ignore) { + + QuickShopAPI.getPluginInstance().getLogger().warning("Error while creating config \"" + file.getName() + "\"."); + return false; + } + return false; + } + + public YamlDocument getYaml() { + + return yaml; + } + + public void setYaml(final YamlDocument yaml) { + + this.yaml = yaml; + } + + public boolean save() { + + try { + + yaml.save(file); + return true; + } catch(final IOException ignore) { + + QuickShopAPI.getPluginInstance().getLogger().warning("Error while saving config \"" + nodes.get(0) + "\"."); + return false; + } + } + + public void setComment(final String route, final List comments) { + + if(yaml.contains(route)) { + yaml.getBlock(route).setComments(comments); + } + } + + public void setComment(final String route, final String comment) { + + setComment(route, Collections.singletonList(comment)); + } + + public @Nullable InputStream getResource(@NotNull final String filename) { + + try { + final URL url = this.getClass().getClassLoader().getResource(filename); + if(url == null) { + return null; + } else { + + final URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + + return connection.getInputStream(); + } + } catch(final IOException ignore) { + return null; + } + } +} \ No newline at end of file diff --git a/quickshop-bukkit/pom.xml b/quickshop-bukkit/pom.xml index 7e0708d6c1..d66c3ab818 100644 --- a/quickshop-bukkit/pom.xml +++ b/quickshop-bukkit/pom.xml @@ -71,6 +71,12 @@ + + org.jetbrains + annotations + 26.0.2-1 + provided + io.papermc.paper paper-api diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java index c523b3a474..4b7ee9e6ed 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java @@ -54,6 +54,8 @@ import com.ghostchu.quickshop.menu.ShopKeeperMenu; import com.ghostchu.quickshop.menu.ShopStaffMenu; import com.ghostchu.quickshop.menu.ShopTradeMenu; +import com.ghostchu.quickshop.menu.config.InteractionConfig; +import com.ghostchu.quickshop.menu.config.MainConfig; import com.ghostchu.quickshop.metric.MetricListener; import com.ghostchu.quickshop.papi.QuickShopPAPI; import com.ghostchu.quickshop.permission.PermissionManager; @@ -84,8 +86,6 @@ import com.ghostchu.quickshop.util.ReflectFactory; import com.ghostchu.quickshop.util.ShopUtil; import com.ghostchu.quickshop.util.Util; -import com.ghostchu.quickshop.util.config.ConfigUpdateScript; -import com.ghostchu.quickshop.util.config.ConfigurationUpdater; import com.ghostchu.quickshop.util.envcheck.CheckResult; import com.ghostchu.quickshop.util.envcheck.EnvCheckEntry; import com.ghostchu.quickshop.util.envcheck.EnvironmentChecker; @@ -115,6 +115,8 @@ import com.google.common.cache.CacheBuilder; import com.tcoded.folialib.FoliaLib; import com.vdurmont.semver4j.Semver; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; import io.papermc.lib.PaperLib; import lombok.Getter; import lombok.Setter; @@ -145,8 +147,6 @@ import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; import org.bukkit.command.PluginCommand; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.ServicePriority; @@ -172,7 +172,6 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; public class QuickShop implements QuickShopAPI, Reloadable { @@ -193,6 +192,7 @@ public class QuickShop implements QuickShopAPI, Reloadable { */ @ApiStatus.Internal private static QuickShop instance; + private MainConfig config; /** * The manager to check permissions. */ @@ -469,15 +469,19 @@ private void initConfiguration() { /* Process the config */ //noinspection ResultOfMethodCallIgnored javaPlugin.getDataFolder().mkdirs(); - try { + + this.config = new MainConfig(); + if(!this.config.load()) { + logger.error("Failed to load config.yml, The binary file of QuickShop may be corrupted. Please re-download from our website."); + } + + /*try { javaPlugin.saveDefaultConfig(); } catch(final IllegalArgumentException resourceNotFoundException) { logger.error("Failed to save config.yml from jar, The binary file of QuickShop may be corrupted. Please re-download from our website."); } - javaPlugin.reloadConfig(); - if(getConfig().getInt("config-version", 0) == 0) { - getConfig().set("config-version", 1); - } + javaPlugin.reloadConfig();*/ + /* It will generate a new UUID above updateConfig */ this.serverUniqueID = UUID.fromString(Objects.requireNonNull(getConfig().getString("server-uuid", String.valueOf(UUID.randomUUID())))); updateConfig(); @@ -550,14 +554,14 @@ public void reloadConfigSubModule() { } @NotNull - public FileConfiguration getConfig() { + public YamlDocument getConfig() { - return javaPlugin.getConfig(); + return this.config.getYaml(); } private void updateConfig() { - new ConfigurationUpdater(this).update(new ConfigUpdateScript(getConfig(), this)); + //new ConfigurationUpdater(this).update(new ConfigUpdateScript(getConfig(), this)); } @NotNull @@ -1078,7 +1082,7 @@ public boolean setupDatabase() { logger.info("Setting up database..."); final HikariConfig config = HikariUtil.createHikariConfig(); try { - final ConfigurationSection dbCfg = getConfig().getConfigurationSection("database"); + final Section dbCfg = getConfig().getSection("database"); if(Objects.requireNonNull(dbCfg).getBoolean("mysql")) { databaseDriverType = DatabaseDriverType.MYSQL; // MySQL database - Required database be created first. @@ -1341,7 +1345,7 @@ public String getMainCommand() { public String getCommandPrefix(final String commandLabel) { - final ConfigurationSection section = getConfig().getConfigurationSection("custom-subcommands"); + final Section section = getConfig().getSection("custom-subcommands"); if(section == null) return commandLabel; final String prefix = section.getString(commandLabel); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Name.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Name.java index f8ecdf6619..8d84117d8e 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Name.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Name.java @@ -71,7 +71,7 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman return; } - final double fee = plugin.getConfig().getDouble("shop.name-fee", 0); + final double fee = plugin.getConfig().getDouble("shop.name-fee", 0.0); QSEconomyTransaction transaction = null; if(fee > 0) { if(!plugin.perm().hasPermission(sender, "quickshop.bypass.namefee")) { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java index 5dddc7a700..6c2de350c8 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java @@ -2,6 +2,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.util.logger.Log; +import dev.dejvokep.boostedyaml.block.implementation.Section; import org.bukkit.configuration.ConfigurationSection; public class HikariUtil { @@ -13,15 +14,17 @@ private HikariUtil() { public static cc.carm.lib.easysql.hikari.HikariConfig createHikariConfig() { final cc.carm.lib.easysql.hikari.HikariConfig config = new cc.carm.lib.easysql.hikari.HikariConfig(); - ConfigurationSection section = QuickShop.getInstance().getConfig().getConfigurationSection("database"); + Section section = QuickShop.getInstance().getConfig().getSection("database"); if(section == null) { throw new IllegalArgumentException("database section in configuration not found"); } - section = section.getConfigurationSection("properties"); + section = section.getSection("properties"); if(section == null) { throw new IllegalArgumentException("database.properties section in configuration not found"); } - for(final String key : section.getKeys(false)) { + for(final Object keyObj : section.getKeys()) { + + final String key = String.valueOf(keyObj); config.addDataSourceProperty(key, String.valueOf(section.get(key))); } Log.debug("HikariCP Config created with properties: " + config.getDataSourceProperties()); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/localization/text/SimpleTextManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/localization/text/SimpleTextManager.java index fb58a62600..797393291b 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/localization/text/SimpleTextManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/localization/text/SimpleTextManager.java @@ -739,7 +739,7 @@ public List getAvailableLanguages() { } @Override - public com.ghostchu.quickshop.api.localization.text.@NotNull TextList ofList(@Nullable final QUser sender, @NotNull final String path, @Nullable final Object... args) { + public @NotNull com.ghostchu.quickshop.api.localization.text.TextList ofList(@Nullable final QUser sender, @NotNull final String path, @Nullable final Object... args) { return new TextList(this, sender, languageFilesManager.getDistributions(), path, tagResolvers, convert(args)); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java new file mode 100644 index 0000000000..aadd4e482c --- /dev/null +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java @@ -0,0 +1,53 @@ +package com.ghostchu.quickshop.menu.config; + + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import com.ghostchu.quickshop.api.config.QSConfig; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; +import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; +import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; + +import java.util.Collections; + +/** + * InteractionConfig + * + * @author creatorfromhell + * @since 6.2.0.11 + */ +public class InteractionConfig extends QSConfig { + + private static InteractionConfig instance; + + public InteractionConfig() { + + super("interaction.yml", "interaction.yml", Collections.emptyList(), + LoaderSettings.builder().setAutoUpdate(true).build(), + UpdaterSettings.builder().setAutoSave(true).setVersioning(new BasicVersioning("version")).build()); + + instance = this; + } + + public static YamlDocument yaml() { + + return instance.getYaml(); + } +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java new file mode 100644 index 0000000000..952be2692a --- /dev/null +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java @@ -0,0 +1,53 @@ +package com.ghostchu.quickshop.menu.config; + + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import com.ghostchu.quickshop.api.config.QSConfig; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; +import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; +import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; + +import java.util.Collections; + +/** + * MainConfig + * + * @author creatorfromhell + * @since 6.2.0.11 + */ +public class MainConfig extends QSConfig { + + private static MainConfig instance; + + public MainConfig() { + + super("config.yml", "config.yml", Collections.emptyList(), + LoaderSettings.builder().setAutoUpdate(true).build(), + UpdaterSettings.builder().setAutoSave(true).setVersioning(new BasicVersioning("config-version")).build()); + + instance = this; + } + + public static YamlDocument yaml() { + + return instance.getYaml(); + } +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/papi/PAPICache.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/papi/PAPICache.java index 7a242f23c8..fa5ed410f4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/papi/PAPICache.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/papi/PAPICache.java @@ -33,7 +33,7 @@ public PAPICache() { private void init() { this.plugin = QuickShop.getInstance(); - this.expiredTime = plugin.getConfig().getLong("plugin.PlaceHolderAPI.cache", 900000); + this.expiredTime = plugin.getConfig().getLong("plugin.PlaceHolderAPI.cache", 900000L); this.performCaches = CacheBuilder.newBuilder() .expireAfterWrite(expiredTime, java.util.concurrent.TimeUnit.MILLISECONDS) .recordStats() @@ -43,9 +43,9 @@ private void init() { @NotNull public Optional getCached(@NotNull final UUID player, @NotNull final String args, @NotNull final BiFunction loader) { - try(PerfMonitor ignored = new PerfMonitor("PlaceHolder API Handling")) { + try(final PerfMonitor ignored = new PerfMonitor("PlaceHolder API Handling")) { return performCaches.get(compileUniqueKey(player, args), ()->Optional.ofNullable(loader.apply(player, args))); - } catch(ExecutionException ex) { + } catch(final ExecutionException ex) { plugin.logger().warn("Failed to get cache for " + player + " " + args, ex); return Optional.empty(); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java index 7ef463d597..b6436f3c00 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java @@ -22,6 +22,7 @@ import com.ghostchu.quickshop.api.shop.interaction.InteractionClick; import com.ghostchu.quickshop.api.shop.interaction.InteractionManager; import com.ghostchu.quickshop.api.shop.interaction.InteractionType; +import com.ghostchu.quickshop.menu.config.InteractionConfig; import com.ghostchu.quickshop.shop.interaction.behaviors.ControlPanel; import com.ghostchu.quickshop.shop.interaction.behaviors.ControlPanelUI; import com.ghostchu.quickshop.shop.interaction.behaviors.TradeDirect; @@ -45,14 +46,9 @@ import com.ghostchu.quickshop.util.paste.util.HTMLTable; import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.Reloadable; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.event.player.PlayerInteractEvent; import org.jetbrains.annotations.NotNull; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -246,8 +242,13 @@ public ReloadResult reloadModule() throws Exception { public void loadConfig() { Log.debug("Interaction Config Loading."); + final InteractionConfig config = new InteractionConfig(); + if(!config.load()) { - final File configFile = new File(plugin.getDataFolder(), "interaction.yml"); + plugin.logger().warn("Failed to copy interaction.yml to plugin folder!"); + } + + /*final File configFile = new File(plugin.getDataFolder(), "interaction.yml"); if(!configFile.exists()) { try { Files.copy(plugin.getJavaPlugin().getResource("interaction.yml"), configFile.toPath()); @@ -255,12 +256,12 @@ public void loadConfig() { plugin.logger().warn("Failed to copy interaction.yml to plugin folder!", e); } } - final FileConfiguration config = YamlConfiguration.loadConfiguration(configFile); + final FileConfiguration config = YamlConfiguration.loadConfiguration(configFile);*/ behaviorMapping.clear(); for(final String interaction : interactions.keySet()) { - final String behavior = config.getString(interaction.toUpperCase(Locale.ROOT)); + final String behavior = config.getYaml().getString(interaction.toUpperCase(Locale.ROOT)); if(behavior == null) { continue; } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ChatSheetPrinter.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ChatSheetPrinter.java index d1b4c40b60..023aed7190 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ChatSheetPrinter.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/ChatSheetPrinter.java @@ -67,4 +67,4 @@ public CommandSender getSender() { return p; } -} +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigProvider.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigProvider.java deleted file mode 100644 index f126cb501e..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigProvider.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.ghostchu.quickshop.util.config; - -import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.api.QuickShopInstanceHolder; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - -public class ConfigProvider extends QuickShopInstanceHolder { - - private final File configFile; - private final Logger logger = plugin.getLogger(); - private FileConfiguration config = null; - - public ConfigProvider(final QuickShop plugin, final File configFile) { - - super(plugin.getJavaPlugin()); - this.configFile = configFile; - } - - public @NotNull FileConfiguration get() { - - if(config == null) { - reload(); - } - return config; - } - - public void reload() { - - reload(false); - } - - /** - * Reloads QuickShops config - * - * @param defaults is using default configuration - */ - public void reload(final boolean defaults) { - - if(!configFile.exists()) { - plugin.saveDefaultConfig(); - } - if(config == null) { - config = new YamlConfiguration(); - } - try(final InputStream defaultConfigStream = plugin.getResource(configFile.getName())) { - config.load(configFile); - if(defaultConfigStream != null) { - try(final InputStreamReader reader = new InputStreamReader(defaultConfigStream, StandardCharsets.UTF_8)) { - config.setDefaults(YamlConfiguration.loadConfiguration(reader)); - } - } - } catch(final IOException | InvalidConfigurationException exception) { - if(!defaults) { - logger.log(Level.SEVERE, "Cannot reading the configuration " + configFile.getName() + ", doing backup configuration and use default", exception); - try { - Files.copy(configFile.toPath(), plugin.getDataFolder().toPath().resolve(configFile.getName() + "-broken-" + UUID.randomUUID() + ".yml"), REPLACE_EXISTING); - } catch(final IOException fatalException) { - throw new IllegalStateException("Failed to backup configuration " + configFile.getName(), fatalException); - } - plugin.saveResource(configFile.getName(), true); - reload(true); - } else { - throw new IllegalStateException("Failed to load default configuration " + configFile.getName(), exception); - } - } - - } - - public void save() { - - try { - get().save(configFile); - } catch(final IOException e) { - logger.log(Level.SEVERE, "Failed to save configuration " + configFile.getName(), e); - } - } -} diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigUpdateScript.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigUpdateScript.java deleted file mode 100644 index b8e6ec1f67..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigUpdateScript.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.ghostchu.quickshop.util.config; - -import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.util.Util; -import lombok.Getter; -import org.bukkit.configuration.file.FileConfiguration; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.Collections; -import java.util.List; - -@SuppressWarnings("unused") -public class ConfigUpdateScript { - - private final QuickShop plugin; - @Getter - private final FileConfiguration config; - - public ConfigUpdateScript(@NotNull final FileConfiguration config, @NotNull final QuickShop plugin) { - - this.config = config; - this.plugin = plugin; - } - - @UpdateScript(version = 1035) - public void configLayout() { - - getConfig().set("shop.layout.BUYING.line1", "header"); - getConfig().set("shop.layout.BUYING.line2", "trading"); - getConfig().set("shop.layout.BUYING.line3", "item"); - getConfig().set("shop.layout.BUYING.line4", "price"); - - getConfig().set("shop.layout.FROZEN.line1", "header"); - getConfig().set("shop.layout.FROZEN.line2", "trading"); - getConfig().set("shop.layout.FROZEN.line3", "item"); - getConfig().set("shop.layout.FROZEN.line4", "price"); - - getConfig().set("shop.layout.SELLING.line1", "header"); - getConfig().set("shop.layout.SELLING.line2", "trading"); - getConfig().set("shop.layout.SELLING.line3", "item"); - getConfig().set("shop.layout.SELLING.line4", "price"); - } - - @UpdateScript(version = 1034) - public void configWorldWhitelist() { - - getConfig().set("shop.whitelist-world", Collections.emptyList()); - getConfig().set("database-loading-whitelist-worlds", Collections.emptyList()); - } - - @UpdateScript(version = 1033) - public void configDisplayCoords() { - - getConfig().set("shop.display-coords.x", "0.5"); - getConfig().set("shop.display-coords.y", "0.8"); - getConfig().set("shop.display-coords.z", "0.5"); - } - - @UpdateScript(version = 1032) - public void configProtocol() { - - getConfig().set("shop.display-protocol", "protocollib"); - } - - @UpdateScript(version = 1031) - public void configControlPanel() { - - getConfig().set("shop.control-panel", List.of("owner", "unlimited", "freeze", "shop_mode", - "set_price", "set_amount", "refill", "empty", - "display", "history", "remove")); - getConfig().set("shop.sign-wax", false); - } - - @UpdateScript(version = 1029) - public void configWorldLoadingBlacklist() { - - getConfig().set("database-loading-blacklist-worlds", Collections.emptyList()); - } - - @UpdateScript(version = 1028) - public void configCleanup_1028() { - - getConfig().set("chat-type", null); - getConfig().set("server-platform", null); - } - - @UpdateScript(version = 1027) - public void sqlSectionOptimization() { - - final long connectionTimeoutOld = getConfig().getLong("database.properties.connection-timeout", 60000); - getConfig().set("database.properties.connectionTimeout", connectionTimeoutOld); - final long maximumPoolSizeOld = getConfig().getLong("database.properties.maximum-pool-size", 10); - getConfig().set("database.properties.maximumPoolSize", Math.max(maximumPoolSizeOld, 10)); - final long minimumIdleOld = getConfig().getLong("database.properties.minimum-idle", 10); - getConfig().set("database.properties.minimumIdle", Math.max(maximumPoolSizeOld, 10)); // keep same with maximumPoolSize - - getConfig().set("database.properties.validation-timeout", null); - getConfig().set("database.properties.login-timeout", null); - getConfig().set("database.properties.connection-timeout", null); - getConfig().set("database.properties.maximum-pool-size", null); - getConfig().set("database.properties.minimum-idle", null); - } - - @UpdateScript(version = 1028) - public void donationKey() { - - getConfig().set("donation-key", ""); - } - - @UpdateScript(version = 1029) - public void fixDbSettings() { - - final long connectionTimeoutOld = getConfig().getLong("database.properties.connection-timeout", 60000); - getConfig().set("database.properties.connectionTimeout", connectionTimeoutOld); - final long maximumPoolSizeOld = getConfig().getLong("database.properties.maximum-pool-size", 10); - getConfig().set("database.properties.maximumPoolSize", Math.max(maximumPoolSizeOld, 10)); - final long minimumIdleOld = getConfig().getLong("database.properties.minimum-idle", 10); - getConfig().set("database.properties.minimumIdle", Math.max(maximumPoolSizeOld, 10)); // keep same with maximumPoolSize - - getConfig().set("database.properties.validation-timeout", null); - getConfig().set("database.properties.login-timeout", null); - getConfig().set("database.properties.connection-timeout", null); - getConfig().set("database.properties.maximum-pool-size", null); - getConfig().set("database.properties.minimum-idle", null); - } - - @UpdateScript(version = 1026) - public void toggleEnchantsAndEffectList() { - - getConfig().set("shop.info-panel.show-enchantments", true); - getConfig().set("shop.info-panel.show-effects", true); - getConfig().set("shop.info-panel.show-durability", true); - } - - @UpdateScript(version = 1025) - public void disableCSMByDefault() { - - getConfig().set("bungee-cross-server-msg", false); - } - - @UpdateScript(version = 1024) - public void displayVirtualStatusReset() { - - getConfig().set("shop.display-type", 2); - } - - @UpdateScript(version = 1023) - public void allowPublicKeyRetrieve() { - - if(!getConfig().isSet("database.properties.allowPublicKeyRetrieval")) { - getConfig().set("database.properties.allowPublicKeyRetrieval", true); - } - } - - @UpdateScript(version = 1022) - public void privacySystem() { - - getConfig().set("shop.use-cache", true); - getConfig().set("use-caching", null); - getConfig().set("disabled-metrics", null); - getConfig().set("privacy.type.STATISTIC", true); - getConfig().set("privacy.type.RESEARCH", true); - getConfig().set("privacy.type.DIAGNOSTIC", true); - } - - @UpdateScript(version = 1021) - public void cacheSystemReworked() { - - getConfig().set("shop.use-cache", true); - getConfig().set("use-caching", null); - } - - @UpdateScript(version = 1020) - public void protectionCheckMonitorListeners() { - - getConfig().set("shop.cancel-protection-fake-event-before-reach-monitor-listeners", true); - } - - - @UpdateScript(version = 1019) - public void oldConfigCleanup() { - - getConfig().set("plugin.OpenInv", null); - getConfig().set("plugin.LWC", null); - getConfig().set("plugin.BlockHub", null); - getConfig().set("plugin.NBTAPI", null); - } - - @UpdateScript(version = 1018) - public void tweakBackupPolicy() { - - getConfig().set("purge.backup", null); - getConfig().set("backup-policy.shops-auto-purge", false); - getConfig().set("backup-policy.database-upgrade", true); - getConfig().set("backup-policy.startup", false); - getConfig().set("backup-policy.recovery", true); - } - - @UpdateScript(version = 1017) - public void addQsToCommands() { - - final List getAlias = getConfig().getStringList("custom-commands"); - getAlias.add("qs"); - getConfig().set("custom-commands", getAlias); - } - - @UpdateScript(version = 1016) - public void disableDefaultShopCorruptDeletion() { - - getConfig().set("debug.delete-corrupt-shops", false); - } - - @UpdateScript(version = 1015) - public void removePurgerRefund() { - - getConfig().set("purge.return-create-fee", null); - getConfig().set("shop.async-owner-name-fetch", null); - } - - @UpdateScript(version = 1014) - public void removeDisplayCenterConfig() { - - getConfig().set("shop.display-center", null); - } - - @UpdateScript(version = 1013) - public void asyncOwnerNameFetch() { - - getConfig().set("shop.async-owner-name-fetch", false); - } - - @UpdateScript(version = 1012) - public void displayCenterControl() { - - getConfig().set("shop.display-center", false); - } - - @UpdateScript(version = 1011) - public void removeTryingFixBanlanceInsuffientFeature() { - - getConfig().set("trying-fix-banlance-insuffient", null); - } - - @UpdateScript(version = 1010) - public void allowDisableQsSizeCommandMaxStackSizeCheck() { - - getConfig().set("shop.disable-max-size-check-for-size-command", false); - } - - @UpdateScript(version = 1009) - public void deleteSqlitePlayerMapping() { - - final File f = new File(Util.getCacheFolder(), "player_mapping.db"); - if(f.exists()) { - f.delete(); - } - } - - @UpdateScript(version = 1008) - public void disableTaxForUnlimitedShopAndPerPlayerSignShop() { - - getConfig().set("shop.per-player-shop-sign", false); - getConfig().set("tax-free-for-unlimited-shop", false); - } - - - @UpdateScript(version = 1007) - public void refundFromTaxAccountOption() { - - getConfig().set("shop.refund-from-tax-account", false); - } -} diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationFixer.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationFixer.java deleted file mode 100644 index bc74336142..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationFixer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.ghostchu.quickshop.util.config; - -import com.ghostchu.quickshop.QuickShop; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Objects; - -/** - * ConfigurationFixer is a utilities to help user automatically fix broken configuration. - * - * @author sandtechnology - */ -public class ConfigurationFixer { - - private final QuickShop plugin; - private final File externalConfigFile; - private final FileConfiguration externalConfig; - private final FileConfiguration builtInConfig; - - public ConfigurationFixer(final QuickShop plugin, final File externalConfigFile, final FileConfiguration externalConfig, final FileConfiguration builtInConfig) { - - this.plugin = plugin; - this.externalConfigFile = externalConfigFile; - this.externalConfig = externalConfig; - this.builtInConfig = builtInConfig; - } - - - public boolean fix() { - // There read the default value as true but we should set default value as false in config.yml - // So that we can check the configuration may broken or other else. - if(!externalConfig.getBoolean("config-damaged", true)) { - return false; - } - - plugin.logger().warn("Warning! QuickShop detected the configuration has been corrupted."); - plugin.logger().warn("Backup - Creating backup for configuration..."); - try { - Files.copy(externalConfigFile.toPath(), new File(externalConfigFile.getParent(), externalConfigFile.getName() + "." + System.currentTimeMillis()).toPath()); - } catch(final IOException ioException) { - plugin.logger().warn("Failed to create file backup.", ioException); - } - plugin.logger().warn("Fix - Fixing the configuration, this may take a while..."); - for(final String key : builtInConfig.getKeys(true)) { - final Object value = externalConfig.get(key); - final Object buildInValue = builtInConfig.get(key); - if(!(value instanceof ConfigurationSection) || !value.getClass().getTypeName().equals(Objects.requireNonNull(buildInValue).getClass().getTypeName())) { - plugin.logger().warn("Fixing configuration use default value: {}", key); - plugin.getConfig().set(key, buildInValue); - } - } - plugin.logger().info("QuickShop fixed the corrupted parts in configuration that we can found. We recommend you restart the server and make fix apply."); - externalConfig.set("config-damaged", false); - try { - externalConfig.save(externalConfigFile); - } catch(final IOException e) { - plugin.logger().warn("Couldn't save fixed configuration!", e); - } - return true; - } -} - diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationUpdater.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationUpdater.java deleted file mode 100644 index 115fe469eb..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/ConfigurationUpdater.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.ghostchu.quickshop.util.config; - -import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.common.util.CommonUtil; -import com.ghostchu.quickshop.util.logger.Log; -import lombok.Getter; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.NotNull; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -public class ConfigurationUpdater { - - private static final String CONFIG_VERSION_KEY = "config-version"; - private final QuickShop plugin; - @Getter - private final ConfigurationSection configuration; - private int selectedVersion = -1; - - public ConfigurationUpdater(final QuickShop plugin) { - - this.plugin = plugin; - this.configuration = plugin.getConfig(); - } - - private void brokenConfigurationFix() { - - try(final InputStreamReader buildInConfigReader = new InputStreamReader(new BufferedInputStream(Objects.requireNonNull(plugin.getJavaPlugin().getResource("config.yml"))), StandardCharsets.UTF_8)) { - if(new ConfigurationFixer(plugin, new File(plugin.getDataFolder(), "config.yml"), plugin.getConfig(), YamlConfiguration.loadConfiguration(buildInConfigReader)).fix()) { - plugin.getJavaPlugin().reloadConfig(); - } - } catch(final Exception e) { - plugin.logger().warn("Failed to fix config.yml, plugin may not working properly.", e); - } - } - - public void update(@NotNull final Object configUpdateScript) { - - Log.debug("Starting configuration update..."); - writeServerUniqueId(); - selectedVersion = configuration.getInt(CONFIG_VERSION_KEY, -1); - for(final Method method : getUpdateScripts(configUpdateScript)) { - try { - final UpdateScript updateScript = method.getAnnotation(UpdateScript.class); - final int current = getConfiguration().getInt(CONFIG_VERSION_KEY); - if(current >= updateScript.version()) { - continue; - } - plugin.logger().info("[ConfigUpdater] Updating configuration from " + current + " to " + updateScript.version()); - String scriptName = updateScript.description(); - if(CommonUtil.isEmptyString(scriptName)) { - scriptName = method.getName(); - } - plugin.logger().info("[ConfigUpdater] Executing update script " + scriptName); - try { - if(method.getParameterCount() == 0) { - method.invoke(configUpdateScript); - } else { - if(method.getParameterCount() == 1 && (method.getParameterTypes()[0] == int.class || method.getParameterTypes()[0] == Integer.class)) { - method.invoke(configUpdateScript, current); - } - } - } catch(final Exception e) { - plugin.logger().warn("Failed to execute update script {} for version {}: {}, plugin may not working properly!", method.getName(), updateScript.version(), e.getMessage(), e); - } - getConfiguration().set(CONFIG_VERSION_KEY, updateScript.version()); - plugin.logger().info("[ConfigUpdater] Configuration updated to version " + updateScript.version()); - } catch(final Throwable throwable) { - plugin.logger().warn("Failed execute update script {} for updating to version {}, some configuration options may missing or outdated", method.getName(), method.getAnnotation(UpdateScript.class).version(), throwable); - } - } - plugin.logger().info("[ConfigUpdater] Saving configuration changes..."); - plugin.getJavaPlugin().saveConfig(); - plugin.getJavaPlugin().reloadConfig(); - //Delete old example configuration files - try { - cleanupOldConfigs(); - } catch(final IOException e) { - Log.debug("Failed to cleanup old configuration files: " + e.getMessage()); - } - - } - - private void writeServerUniqueId() { - - final String serverUUID = plugin.getConfig().getString("server-uuid"); - if(serverUUID == null || serverUUID.isEmpty() || !CommonUtil.isUUID(serverUUID)) { - plugin.getConfig().set("server-uuid", UUID.randomUUID().toString()); - } - } - - - @NotNull - public List getUpdateScripts(@NotNull final Object configUpdateScript) { - - final List methods = new ArrayList<>(); - for(final Method declaredMethod : configUpdateScript.getClass().getDeclaredMethods()) { - if(declaredMethod.getAnnotation(UpdateScript.class) == null) { - continue; - } - methods.add(declaredMethod); - } - methods.sort(Comparator.comparingInt(o->o.getAnnotation(UpdateScript.class).version())); - return methods; - } - - private void cleanupOldConfigs() throws IOException { - - Files.deleteIfExists(new File(plugin.getDataFolder(), "example.config.yml").toPath()); - Files.deleteIfExists(new File(plugin.getDataFolder(), "example-configuration.txt").toPath()); - Files.deleteIfExists(new File(plugin.getDataFolder(), "example-configuration.yml").toPath()); - try { - if(new File(plugin.getDataFolder(), "messages.yml").exists()) { - Files.move(new File(plugin.getDataFolder(), "messages.yml").toPath(), new File(plugin.getDataFolder(), "messages.yml.outdated").toPath()); - } - } catch(final Exception ignore) { - } - } -} diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/UpdateScript.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/UpdateScript.java deleted file mode 100644 index 2a6487e8b1..0000000000 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/UpdateScript.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ghostchu.quickshop.util.config; - -import org.jetbrains.annotations.Nullable; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Mark a method as update script and will be executed by ConfigurationUpdater - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Inherited -public @interface UpdateScript { - - @Nullable String description() default ""; - - int version(); -} diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java index 1048f82899..ff98735610 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java @@ -7,6 +7,7 @@ import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.ReloadStatus; import com.ghostchu.simplereloadlib.Reloadable; +import dev.dejvokep.boostedyaml.block.implementation.Section; import org.bukkit.OfflinePlayer; import org.bukkit.attribute.Attribute; import org.bukkit.block.ShulkerBox; @@ -67,7 +68,7 @@ public QuickShopItemMatcherImpl(final QuickShop plugin, final ItemMetaMatcher it private void init() { - itemMetaMatcher = new ItemMetaMatcher(plugin.getConfig().getConfigurationSection("matcher.item"), this); + itemMetaMatcher = new ItemMetaMatcher(plugin.getConfig().getSection("matcher.item"), this); workType = plugin.getConfig().getInt("matcher.work-type"); } @@ -215,7 +216,7 @@ private static class ItemMetaMatcher { private final List matcherList = new ArrayList<>(); - public ItemMetaMatcher(@NotNull final ConfigurationSection itemMatcherConfig, @NotNull final QuickShopItemMatcherImpl itemMatcher) { + public ItemMetaMatcher(@NotNull final Section itemMatcherConfig, @NotNull final QuickShopItemMatcherImpl itemMatcher) { final QuickShop plugin = QuickShop.getInstance(); addIfEnable(itemMatcherConfig, "damage", (meta1, meta2)->{ @@ -534,7 +535,7 @@ public ItemMetaMatcher(@NotNull final ConfigurationSection itemMatcherConfig, @N })); } - private void addIfEnable(final ConfigurationSection itemMatcherConfig, final String path, final Matcher matcher) { + private void addIfEnable(final Section itemMatcherConfig, final String path, final Matcher matcher) { if(itemMatcherConfig.getBoolean(path)) { matcherList.add(matcher); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/paste/item/ChatProcessorInfoItem.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/paste/item/ChatProcessorInfoItem.java index 694df59234..dd5dda5acb 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/paste/item/ChatProcessorInfoItem.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/paste/item/ChatProcessorInfoItem.java @@ -3,7 +3,6 @@ import com.ghostchu.quickshop.common.util.CommonUtil; import com.ghostchu.quickshop.util.paste.util.HTMLTable; import net.kyori.adventure.Adventure; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.viaversion.ViaFacet; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer; @@ -42,7 +41,6 @@ private String buildContent() { table.insert("Processor", IMPL); table.insert("Formatter", FORMATTER); table.insert("Adventure API", CommonUtil.getClassPathRelative(Adventure.class)); - table.insert("Adventure Bukkit Platform", CommonUtil.getClassPathRelative(BukkitAudiences.class)); table.insert("Adventure Text Serializer (Legacy)", CommonUtil.getClassPathRelative(LegacyComponentSerializer.class)); table.insert("Adventure Text Serializer (Gson)", CommonUtil.getClassPathRelative(GsonComponentSerializer.class)); table.insert("Adventure Text Serializer (Json)", CommonUtil.getClassPathRelative(JSONComponentSerializer.class)); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java index b6567fbafa..d8ee5de1a8 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java @@ -3,6 +3,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.util.logger.Log; import com.ghostchu.quickshop.util.metric.MetricDataType; +import dev.dejvokep.boostedyaml.block.implementation.Section; import org.bukkit.configuration.ConfigurationSection; import java.util.UUID; @@ -18,12 +19,12 @@ public PrivacyController(final QuickShop plugin) { private boolean isAllowed(final MetricDataType dataType, final String moduleName, final UUID transaction) { - final ConfigurationSection section = plugin.getConfig().getConfigurationSection("privacy"); + final Section section = plugin.getConfig().getSection("privacy"); if(section == null) { Log.privacy("[CHECK] Transaction " + transaction + " was declined: `privacy` ROOT section missing."); return false; } - final ConfigurationSection dataTypeSection = section.getConfigurationSection("type"); + final Section dataTypeSection = section.getSection("type"); if(dataTypeSection == null) { Log.privacy("[CHECK] Transaction " + transaction + " was declined: `type` section missing."); return false; @@ -33,7 +34,7 @@ private boolean isAllowed(final MetricDataType dataType, final String moduleName Log.privacy("[CHECK] Transaction " + transaction + " was declined: item in `type` section missing."); return false; } - final ConfigurationSection moduleSection = section.getConfigurationSection("module"); + final Section moduleSection = section.getSection("module"); if(moduleSection == null) { Log.privacy("[CHECK] Transaction " + transaction + " was approved: `module` section not found but " + dataType.name() + " type was explicitly enabled."); return true; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/watcher/UpdateWatcher.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/watcher/UpdateWatcher.java index 4611219623..322c835e4b 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/watcher/UpdateWatcher.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/watcher/UpdateWatcher.java @@ -35,7 +35,6 @@ public void init() { if(plugin.perm().hasPermission(player, "quickshop.alerts")) { MsgUtil.sendDirectMessage(player, ChatColor.GREEN + "---------------------------------------------------"); MsgUtil.sendDirectMessage(player, ChatColor.GREEN + LegacyComponentSerializer.legacySection().serialize(pickRandomMessage(player))); -// MsgUtil.sendDirectMessage(player, ChatColor.GREEN + "Type command " + ChatColor.YELLOW + "/quickshop update" + ChatColor.GREEN + " or click the link below to update QuickShop :)"); MsgUtil.sendDirectMessage(player, Component.text("https://modrinth.com/plugin/quickshop-hikari").color(NamedTextColor.AQUA).clickEvent(ClickEvent.openUrl("https://modrinth.com/plugin/quickshop-hikari"))); MsgUtil.sendDirectMessage(player, ChatColor.GREEN + "---------------------------------------------------"); } diff --git a/quickshop-bukkit/src/main/resources/interaction.yml b/quickshop-bukkit/src/main/resources/interaction.yml index 57e2913fb6..f2884c28f2 100644 --- a/quickshop-bukkit/src/main/resources/interaction.yml +++ b/quickshop-bukkit/src/main/resources/interaction.yml @@ -1,10 +1,10 @@ -version: 2 +version: 3 # Chat Mode: STANDING_LEFT_CLICK_SIGN: TRADE_INTERACTION STANDING_RIGHT_CLICK_SIGN: CONTROL_PANEL STANDING_LEFT_CLICK_SHOPBLOCK: TRADE_INTERACTION -STANDING_RIGHT_CLICK_SHOPBLOCK: NONE +STANDING_RIGHT_CLICK_SHOPBLOCK: NONE # Reserve for open chest STANDING_LEFT_CLICK_CONTAINER: TRADE_INTERACTION STANDING_RIGHT_CLICK_CONTAINER: NONE SNEAKING_LEFT_CLICK_CONTAINER: NONE @@ -12,7 +12,7 @@ SNEAKING_RIGHT_CLICK_CONTAINER: NONE SNEAKING_LEFT_CLICK_SIGN: NONE SNEAKING_RIGHT_CLICK_SIGN: NONE SNEAKING_LEFT_CLICK_SHOPBLOCK: NONE -SNEAKING_RIGHT_CLICK_SHOPBLOCK: NONE +SNEAKING_RIGHT_CLICK_SHOPBLOCK: NONE # Reserve for open chest # GUI Mode - Opens GUI menus for trading and shop management diff --git a/quickshop-common/pom.xml b/quickshop-common/pom.xml index 41b265bdb1..6eda71c9f3 100644 --- a/quickshop-common/pom.xml +++ b/quickshop-common/pom.xml @@ -96,6 +96,10 @@ net.kyori examination-string + + org.jetbrains + annotations + @@ -116,6 +120,10 @@ net.kyori examination-string + + org.jetbrains + annotations + @@ -132,6 +140,10 @@ org.slf4j slf4j-api + + org.jetbrains + annotations + @@ -144,6 +156,10 @@ net.kyori adventure-api + + org.jetbrains + annotations + @@ -156,6 +172,10 @@ net.kyori adventure-api + + org.jetbrains + annotations + @@ -172,6 +192,10 @@ net.kyori option + + org.jetbrains + annotations + @@ -184,6 +208,10 @@ net.kyori adventure-api + + org.jetbrains + annotations + @@ -196,6 +224,10 @@ net.kyori adventure-api + + org.jetbrains + annotations + @@ -203,6 +235,16 @@ examination-api provided ${depend.examination-api} + + + net.kyori + examination-api + + + org.jetbrains + annotations + + net.kyori @@ -214,6 +256,10 @@ net.kyori examination-api + + org.jetbrains + annotations + From 197906d4f22419d7a9a9230e56b4520027987006 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Thu, 1 Jan 2026 21:24:46 -0500 Subject: [PATCH 12/19] Prevent duplicate shop deletions by introducing an `inDeletion` queue and ensuring cleanup after operation completion. Update changelog. --- .changelog/6.2.0.11.md | 7 ++++++- .../ghostchu/quickshop/shop/SimpleShopManager.java | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.changelog/6.2.0.11.md b/.changelog/6.2.0.11.md index 31eedc7e26..9531026a62 100644 --- a/.changelog/6.2.0.11.md +++ b/.changelog/6.2.0.11.md @@ -98,6 +98,9 @@ - Deprecated the ShopType enum, and the ShopTypeEvent. These are replaced with the new IShopType interface, and implementing classes. - The new event is ShopTypeEnhancedEvent. - This allows for third-party addons to add their own shop types, and simplifies code checks for translations. +- Updated config system to use Boosted-yaml + - this allows for auto-updating of config files without complex update scripts + - this also allows for the config system to not be minecraft-dependent. ## Minor Changes - Bump Java version up to 21 @@ -129,4 +132,6 @@ - Fixes for lands addon on folia, Use regionThread for Folia compatibility(thanks to YusakiDev) - Fixes for TRADE_DIRECT on stacking shops - Fixes issue for protocollib 5.4.0 with undocumented packet changes. -- Fixes issue where item stacks could be used on creation above the max stack size. \ No newline at end of file +- Fixes issue where item stacks could be used on creation above the max stack size. +- Fixes issue with BukkitAudiences-related crashes on 1.21.11. +- Fix issue with shop deletion when getting too impatient. \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java index 2bb5e0b14d..59a71dbfd4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/SimpleShopManager.java @@ -89,6 +89,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -101,6 +102,7 @@ public class SimpleShopManager extends AbstractShopManager implements ShopManage public static final String DEFAULT_TYPE = "BUYING"; protected final Map shopTypes = Maps.newConcurrentMap(); + protected final ConcurrentLinkedQueue inDeletion = new ConcurrentLinkedQueue<>(); protected final InteractiveManager interactiveManager; @Getter @@ -1273,8 +1275,16 @@ private int buyingShopAllCalc(@NotNull final EconomyProvider eco, @NotNull final @Override public void deleteShop(@NotNull final Shop shop) { + if(inDeletion.contains(shop.getShopId())) { + + //if we're already in deletion, don't do anything + return; + } + + inDeletion.add(shop.getShopId()); ShopDeleteEvent shopDeleteEvent = new ShopDeleteEvent(shop, false); if(shopDeleteEvent.callCancellableEvent()) { + inDeletion.remove(shop.getShopId()); Log.debug("Shop delete was cancelled by 3rd-party plugin"); return; } @@ -1286,6 +1296,7 @@ public void deleteShop(@NotNull final Shop shop) { unregisterShop(shop, true); shopDeleteEvent = shopDeleteEvent.clone(Phase.POST); shopDeleteEvent.callEvent(); + inDeletion.remove(shop.getShopId()); } From a94f3c77feac52d46189f183f9ecf3b27fbf4930 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sat, 3 Jan 2026 00:54:37 -0500 Subject: [PATCH 13/19] Refactor: Move `menu.config` package to `config` package for better organization and consistency. --- .../src/main/java/com/ghostchu/quickshop/QuickShop.java | 8 ++++---- .../ghostchu/quickshop/{menu => }/config/GuiConfig.java | 2 +- .../quickshop/{menu => }/config/GuiIconBuilder.java | 2 +- .../quickshop/{menu => }/config/InteractionConfig.java | 2 +- .../ghostchu/quickshop/{menu => }/config/MainConfig.java | 2 +- .../java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java | 2 +- .../java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java | 2 +- .../java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java | 2 +- .../java/com/ghostchu/quickshop/menu/ShopStaffMenu.java | 2 +- .../java/com/ghostchu/quickshop/menu/ShopTradeMenu.java | 2 +- .../ghostchu/quickshop/menu/browse/GroupedItemPage.java | 2 +- .../java/com/ghostchu/quickshop/menu/browse/MainPage.java | 2 +- .../com/ghostchu/quickshop/menu/browse/ShopListPage.java | 2 +- .../com/ghostchu/quickshop/menu/history/MainPage.java | 2 +- .../java/com/ghostchu/quickshop/menu/keeper/MainPage.java | 2 +- .../com/ghostchu/quickshop/menu/shared/QuickShopPage.java | 2 +- .../quickshop/menu/staff/PlayerSelectionPage.java | 2 +- .../ghostchu/quickshop/menu/staff/StaffSelectionPage.java | 2 +- .../java/com/ghostchu/quickshop/menu/trade/MainPage.java | 2 +- .../shop/interaction/QuickShopInteractionManager.java | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) rename quickshop-bukkit/src/main/java/com/ghostchu/quickshop/{menu => }/config/GuiConfig.java (99%) rename quickshop-bukkit/src/main/java/com/ghostchu/quickshop/{menu => }/config/GuiIconBuilder.java (99%) rename quickshop-bukkit/src/main/java/com/ghostchu/quickshop/{menu => }/config/InteractionConfig.java (97%) rename quickshop-bukkit/src/main/java/com/ghostchu/quickshop/{menu => }/config/MainConfig.java (97%) diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java index 4b7ee9e6ed..168ad78b14 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java @@ -31,6 +31,7 @@ import com.ghostchu.quickshop.common.util.CommonUtil; import com.ghostchu.quickshop.common.util.JsonUtil; import com.ghostchu.quickshop.common.util.QuickExecutor; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.database.DatabaseIOUtil; import com.ghostchu.quickshop.database.HikariUtil; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; @@ -54,8 +55,7 @@ import com.ghostchu.quickshop.menu.ShopKeeperMenu; import com.ghostchu.quickshop.menu.ShopStaffMenu; import com.ghostchu.quickshop.menu.ShopTradeMenu; -import com.ghostchu.quickshop.menu.config.InteractionConfig; -import com.ghostchu.quickshop.menu.config.MainConfig; +import com.ghostchu.quickshop.config.MainConfig; import com.ghostchu.quickshop.metric.MetricListener; import com.ghostchu.quickshop.papi.QuickShopPAPI; import com.ghostchu.quickshop.permission.PermissionManager; @@ -218,7 +218,7 @@ public class QuickShop implements QuickShopAPI, Reloadable { protected HelperMethods helperMethods; private QuickShopInteractionManager interactionManager; @Getter - private com.ghostchu.quickshop.menu.config.GuiConfig guiConfig; + private GuiConfig guiConfig; private FoliaLib folia; /* Public QuickShop API End */ private GameVersion gameVersion; @@ -738,7 +738,7 @@ public final void onEnable() { } // Initialize GuiConfig before menus that depend on it - this.guiConfig = new com.ghostchu.quickshop.menu.config.GuiConfig(this); + this.guiConfig = new GuiConfig(this); MenuManager.instance().addMenu(new ShopHistoryMenu()); MenuManager.instance().addMenu(new ShopKeeperMenu()); diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java similarity index 99% rename from quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java rename to quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java index d301db333f..ccfa76ab70 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java @@ -1,4 +1,4 @@ -package com.ghostchu.quickshop.menu.config; +package com.ghostchu.quickshop.config; /* * QuickShop-Hikari * Copyright (C) 2024 Daniel "creatorfromhell" Vidmar diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiIconBuilder.java similarity index 99% rename from quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java rename to quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiIconBuilder.java index b849d63b97..8123515450 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/GuiIconBuilder.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiIconBuilder.java @@ -1,4 +1,4 @@ -package com.ghostchu.quickshop.menu.config; +package com.ghostchu.quickshop.config; /* * QuickShop-Hikari * Copyright (C) 2024 Daniel "creatorfromhell" Vidmar diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/InteractionConfig.java similarity index 97% rename from quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java rename to quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/InteractionConfig.java index aadd4e482c..5229444e36 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/InteractionConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/InteractionConfig.java @@ -1,4 +1,4 @@ -package com.ghostchu.quickshop.menu.config; +package com.ghostchu.quickshop.config; /* diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java similarity index 97% rename from quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java rename to quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java index 952be2692a..2ac6626dce 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/config/MainConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java @@ -1,4 +1,4 @@ -package com.ghostchu.quickshop.menu.config; +package com.ghostchu.quickshop.config; /* diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java index 42703811c5..c79d22df31 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java @@ -20,7 +20,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.menu.browse.GroupedItemPage; import com.ghostchu.quickshop.menu.browse.ShopListPage; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import net.tnemc.menu.core.Menu; import net.tnemc.menu.core.Page; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java index 20e6038c42..59dd7ec036 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopHistoryMenu.java @@ -18,7 +18,7 @@ */ import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.history.MainPage; import net.tnemc.menu.core.Menu; import net.tnemc.menu.core.Page; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java index 693a82e052..adb5d97ae5 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopKeeperMenu.java @@ -18,7 +18,7 @@ */ import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.keeper.MainPage; import com.ghostchu.quickshop.menu.shared.QuickShopMenu; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java index 583649c151..a4f21947e7 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopStaffMenu.java @@ -18,7 +18,7 @@ */ import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.QuickShopMenu; import com.ghostchu.quickshop.menu.staff.PlayerSelectionPage; import com.ghostchu.quickshop.menu.staff.StaffSelectionPage; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java index fc095e2268..09637a3e2a 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopTradeMenu.java @@ -18,7 +18,7 @@ */ import com.ghostchu.quickshop.QuickShop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.QuickShopMenu; import com.ghostchu.quickshop.menu.trade.MainPage; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java index a2d2704c29..1add06dff0 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/GroupedItemPage.java @@ -20,7 +20,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.common.util.CommonUtil; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.ClearSearchAction; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import net.kyori.adventure.text.Component; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java index 50548ec551..1b64f3d63c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/MainPage.java @@ -21,7 +21,7 @@ import com.ghostchu.quickshop.api.economy.EconomyProvider; import com.ghostchu.quickshop.api.obj.QUser; import com.ghostchu.quickshop.api.shop.Shop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import net.kyori.adventure.text.Component; import net.tnemc.item.AbstractItemStack; import net.tnemc.item.bukkit.BukkitItemStack; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java index c401fc4d8c..fc2e06c297 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java @@ -20,7 +20,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.common.util.CommonUtil; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import io.papermc.lib.PaperLib; import net.kyori.adventure.text.Component; import net.tnemc.item.AbstractItemStack; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java index c020791745..71b39b945b 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/history/MainPage.java @@ -22,7 +22,7 @@ import com.ghostchu.quickshop.api.localization.text.ProxiedLocale; import com.ghostchu.quickshop.api.obj.QUser; import com.ghostchu.quickshop.api.shop.Shop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.obj.QUserImpl; import com.ghostchu.quickshop.shop.history.ShopHistory; import com.ghostchu.quickshop.util.Util; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java index 5d3bd08dfc..4cf4255afd 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/keeper/MainPage.java @@ -20,7 +20,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermission; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import com.ghostchu.quickshop.menu.shared.QuickShopPage; import com.ghostchu.quickshop.obj.QUserImpl; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java index 2946b7205c..4990f40697 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/shared/QuickShopPage.java @@ -19,7 +19,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.shop.Shop; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import net.kyori.adventure.text.Component; import net.tnemc.menu.core.Page; import net.tnemc.menu.core.viewer.MenuViewer; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java index 133c764aec..63fb478a31 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/PlayerSelectionPage.java @@ -20,7 +20,7 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermissionGroup; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.ClearSearchAction; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import net.tnemc.item.providers.SkullProfile; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java index c3615d841e..db387b9d91 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/staff/StaffSelectionPage.java @@ -21,7 +21,7 @@ import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermission; import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermissionGroup; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.ClearSearchAction; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import com.ghostchu.quickshop.menu.shared.QuickShopPage; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java index 65336da688..c76de83717 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java @@ -23,7 +23,7 @@ import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.api.shop.ShopAction; import com.ghostchu.quickshop.menu.browse.MarketUtils; -import com.ghostchu.quickshop.menu.config.GuiConfig; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import com.ghostchu.quickshop.menu.shared.PageSwitchWithCloseAction; import com.ghostchu.quickshop.menu.shared.QuickShopPage; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java index b6436f3c00..fcd2e1cb32 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/interaction/QuickShopInteractionManager.java @@ -22,7 +22,7 @@ import com.ghostchu.quickshop.api.shop.interaction.InteractionClick; import com.ghostchu.quickshop.api.shop.interaction.InteractionManager; import com.ghostchu.quickshop.api.shop.interaction.InteractionType; -import com.ghostchu.quickshop.menu.config.InteractionConfig; +import com.ghostchu.quickshop.config.InteractionConfig; import com.ghostchu.quickshop.shop.interaction.behaviors.ControlPanel; import com.ghostchu.quickshop.shop.interaction.behaviors.ControlPanelUI; import com.ghostchu.quickshop.shop.interaction.behaviors.TradeDirect; From 31b0301cff577982ab34cc4e484536fe6f37198e Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sat, 3 Jan 2026 00:57:17 -0500 Subject: [PATCH 14/19] Add ignored route for config updater to skip `limits.ranks` path during auto-updates. --- .../main/java/com/ghostchu/quickshop/config/MainConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java index 2ac6626dce..dc7dbb7296 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java @@ -1,6 +1,5 @@ package com.ghostchu.quickshop.config; - /* * QuickShop-Hikari * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar @@ -41,7 +40,9 @@ public MainConfig() { super("config.yml", "config.yml", Collections.emptyList(), LoaderSettings.builder().setAutoUpdate(true).build(), - UpdaterSettings.builder().setAutoSave(true).setVersioning(new BasicVersioning("config-version")).build()); + UpdaterSettings.builder().setAutoSave(true) + .setVersioning(new BasicVersioning("config-version")) + .addIgnoredRoute("1033", "limits.ranks", '.').build()); instance = this; } From bfdc9d95fbb32d6bb724044630ff8d7a6eae71d4 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sun, 4 Jan 2026 11:35:16 -0500 Subject: [PATCH 15/19] Refactor configuration management: migrate `MainConfig` and `GuiConfig` to extend `QSConfig`, streamline loading processes, and integrate `Simplereloadlib` for reload functionality. --- addon/betonquest/pom.xml | 2 +- addon/bluemap/pom.xml | 2 +- addon/discordsrv/pom.xml | 2 +- addon/discount/pom.xml | 2 +- addon/displaycontrol/pom.xml | 2 +- addon/dynmap/pom.xml | 2 +- addon/limited/pom.xml | 2 +- addon/list/pom.xml | 2 +- addon/pl3xmap/pom.xml | 2 +- addon/plan/pom.xml | 2 +- addon/quests/pom.xml | 2 +- addon/reremake-migrator/pom.xml | 2 +- addon/shopitemonly/pom.xml | 2 +- addon/squaremap/pom.xml | 2 +- compatibility/advancedregionmarket/pom.xml | 2 +- compatibility/angelchest/pom.xml | 2 +- compatibility/bentobox/pom.xml | 2 +- compatibility/bungeecord-geyser/pom.xml | 2 +- compatibility/bungeecord/pom.xml | 2 +- compatibility/chestprotect/pom.xml | 2 +- compatibility/clearlag/pom.xml | 2 +- compatibility/common/pom.xml | 2 +- compatibility/dominion/pom.xml | 2 +- compatibility/ecoenchants/pom.xml | 2 +- compatibility/elitemobs/pom.xml | 2 +- compatibility/fabledskyblock/pom.xml | 2 +- compatibility/griefprevention/pom.xml | 2 +- compatibility/husktowns/pom.xml | 2 +- compatibility/iridiumskyblock/pom.xml | 2 +- compatibility/itemsadder/pom.xml | 2 +- compatibility/lands/pom.xml | 2 +- compatibility/matcherplus/pom.xml | 2 +- compatibility/openinv/pom.xml | 2 +- compatibility/plotsquared/pom.xml | 2 +- compatibility/reforges/pom.xml | 2 +- compatibility/residence/pom.xml | 2 +- compatibility/simpleclaimsystem/pom.xml | 2 +- compatibility/slimefun/pom.xml | 2 +- compatibility/superiorskyblock/pom.xml | 2 +- compatibility/towny/pom.xml | 2 +- compatibility/ultimateclaims/pom.xml | 2 +- compatibility/velocity/pom.xml | 2 +- compatibility/voidchest/pom.xml | 2 +- compatibility/worldedit/pom.xml | 2 +- compatibility/worldguard/pom.xml | 2 +- downloads.json | 5 + platform/quickshop-platform-interface/pom.xml | 2 +- platform/quickshop-platform-paper/pom.xml | 2 +- pom.xml | 2 +- quickshop-api/pom.xml | 2 +- .../quickshop/api/config/QSConfig.java | 15 +- quickshop-bukkit/pom.xml | 2 +- .../com/ghostchu/quickshop/QuickShop.java | 2 +- .../ghostchu/quickshop/config/GuiConfig.java | 180 +++++++++--------- .../ghostchu/quickshop/config/MainConfig.java | 8 +- quickshop-common/pom.xml | 2 +- 56 files changed, 167 insertions(+), 145 deletions(-) diff --git a/addon/betonquest/pom.xml b/addon/betonquest/pom.xml index 4f3f221160..14749f5b4c 100644 --- a/addon/betonquest/pom.xml +++ b/addon/betonquest/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/addon/bluemap/pom.xml b/addon/bluemap/pom.xml index 0e465a5624..784d065501 100644 --- a/addon/bluemap/pom.xml +++ b/addon/bluemap/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/discordsrv/pom.xml b/addon/discordsrv/pom.xml index 86d0fb5d46..9c90f7db28 100644 --- a/addon/discordsrv/pom.xml +++ b/addon/discordsrv/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/discount/pom.xml b/addon/discount/pom.xml index c1e1124459..adcbb38395 100644 --- a/addon/discount/pom.xml +++ b/addon/discount/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/displaycontrol/pom.xml b/addon/displaycontrol/pom.xml index 74065a8155..6e8e55573e 100644 --- a/addon/displaycontrol/pom.xml +++ b/addon/displaycontrol/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/dynmap/pom.xml b/addon/dynmap/pom.xml index 00227c85ff..86bf390185 100644 --- a/addon/dynmap/pom.xml +++ b/addon/dynmap/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/limited/pom.xml b/addon/limited/pom.xml index d3341874d0..8391a6ce54 100644 --- a/addon/limited/pom.xml +++ b/addon/limited/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/list/pom.xml b/addon/list/pom.xml index 087ee46b8d..566d4fd2a7 100644 --- a/addon/list/pom.xml +++ b/addon/list/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/pl3xmap/pom.xml b/addon/pl3xmap/pom.xml index b301751cc2..bc78eec3c7 100644 --- a/addon/pl3xmap/pom.xml +++ b/addon/pl3xmap/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/plan/pom.xml b/addon/plan/pom.xml index 52711c5100..de5047d65d 100644 --- a/addon/plan/pom.xml +++ b/addon/plan/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/quests/pom.xml b/addon/quests/pom.xml index 874dce33d6..fc30f977aa 100644 --- a/addon/quests/pom.xml +++ b/addon/quests/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/addon/reremake-migrator/pom.xml b/addon/reremake-migrator/pom.xml index f890a9815a..47dceeb6f1 100644 --- a/addon/reremake-migrator/pom.xml +++ b/addon/reremake-migrator/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/shopitemonly/pom.xml b/addon/shopitemonly/pom.xml index 4aa876429e..8c6462b652 100644 --- a/addon/shopitemonly/pom.xml +++ b/addon/shopitemonly/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/addon/squaremap/pom.xml b/addon/squaremap/pom.xml index f8b9443c76..b494f187fa 100644 --- a/addon/squaremap/pom.xml +++ b/addon/squaremap/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.addon diff --git a/compatibility/advancedregionmarket/pom.xml b/compatibility/advancedregionmarket/pom.xml index 866b51ea85..67bc7b214e 100644 --- a/compatibility/advancedregionmarket/pom.xml +++ b/compatibility/advancedregionmarket/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/angelchest/pom.xml b/compatibility/angelchest/pom.xml index d8c8e3ac4d..0dc4899e72 100644 --- a/compatibility/angelchest/pom.xml +++ b/compatibility/angelchest/pom.xml @@ -6,7 +6,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/bentobox/pom.xml b/compatibility/bentobox/pom.xml index ad9202d161..573bd97fa0 100644 --- a/compatibility/bentobox/pom.xml +++ b/compatibility/bentobox/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/bungeecord-geyser/pom.xml b/compatibility/bungeecord-geyser/pom.xml index b533d7eeb3..a8fc928deb 100644 --- a/compatibility/bungeecord-geyser/pom.xml +++ b/compatibility/bungeecord-geyser/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/bungeecord/pom.xml b/compatibility/bungeecord/pom.xml index d170148dfb..fec9aaab49 100644 --- a/compatibility/bungeecord/pom.xml +++ b/compatibility/bungeecord/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/chestprotect/pom.xml b/compatibility/chestprotect/pom.xml index ee9f8beff8..fb368cdd2b 100644 --- a/compatibility/chestprotect/pom.xml +++ b/compatibility/chestprotect/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/clearlag/pom.xml b/compatibility/clearlag/pom.xml index 5333cb89ca..6a3d49a9a0 100644 --- a/compatibility/clearlag/pom.xml +++ b/compatibility/clearlag/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/common/pom.xml b/compatibility/common/pom.xml index 57a003c554..4b0adea6d3 100644 --- a/compatibility/common/pom.xml +++ b/compatibility/common/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/dominion/pom.xml b/compatibility/dominion/pom.xml index cafed504c7..65591d90b7 100644 --- a/compatibility/dominion/pom.xml +++ b/compatibility/dominion/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/ecoenchants/pom.xml b/compatibility/ecoenchants/pom.xml index 02f45f9513..e43813753b 100644 --- a/compatibility/ecoenchants/pom.xml +++ b/compatibility/ecoenchants/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/elitemobs/pom.xml b/compatibility/elitemobs/pom.xml index f07e0e2e20..f5c2a9ca04 100644 --- a/compatibility/elitemobs/pom.xml +++ b/compatibility/elitemobs/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/fabledskyblock/pom.xml b/compatibility/fabledskyblock/pom.xml index 1024007f90..589ce19e3f 100644 --- a/compatibility/fabledskyblock/pom.xml +++ b/compatibility/fabledskyblock/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/griefprevention/pom.xml b/compatibility/griefprevention/pom.xml index ca30d20375..3059699ddb 100644 --- a/compatibility/griefprevention/pom.xml +++ b/compatibility/griefprevention/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/husktowns/pom.xml b/compatibility/husktowns/pom.xml index 314515b032..b7a91ba18e 100644 --- a/compatibility/husktowns/pom.xml +++ b/compatibility/husktowns/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/iridiumskyblock/pom.xml b/compatibility/iridiumskyblock/pom.xml index 96fa9a2ecb..c07fbda0de 100644 --- a/compatibility/iridiumskyblock/pom.xml +++ b/compatibility/iridiumskyblock/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/itemsadder/pom.xml b/compatibility/itemsadder/pom.xml index 508a1e093d..d2e1cfcbd0 100644 --- a/compatibility/itemsadder/pom.xml +++ b/compatibility/itemsadder/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/lands/pom.xml b/compatibility/lands/pom.xml index 51a2960c06..a28d2667db 100644 --- a/compatibility/lands/pom.xml +++ b/compatibility/lands/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/matcherplus/pom.xml b/compatibility/matcherplus/pom.xml index e058bb7f1a..09e77056d2 100644 --- a/compatibility/matcherplus/pom.xml +++ b/compatibility/matcherplus/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/openinv/pom.xml b/compatibility/openinv/pom.xml index 218a72ed03..2469e9ed9f 100644 --- a/compatibility/openinv/pom.xml +++ b/compatibility/openinv/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/plotsquared/pom.xml b/compatibility/plotsquared/pom.xml index b96ea19afe..2b73e730ea 100644 --- a/compatibility/plotsquared/pom.xml +++ b/compatibility/plotsquared/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/reforges/pom.xml b/compatibility/reforges/pom.xml index 0c899a8c2f..624122684b 100644 --- a/compatibility/reforges/pom.xml +++ b/compatibility/reforges/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/residence/pom.xml b/compatibility/residence/pom.xml index 2362d04287..f6dc679659 100644 --- a/compatibility/residence/pom.xml +++ b/compatibility/residence/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/simpleclaimsystem/pom.xml b/compatibility/simpleclaimsystem/pom.xml index 1de176e77f..eed2dbfe2a 100644 --- a/compatibility/simpleclaimsystem/pom.xml +++ b/compatibility/simpleclaimsystem/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/slimefun/pom.xml b/compatibility/slimefun/pom.xml index e286d467bf..0e0ae8c8a9 100644 --- a/compatibility/slimefun/pom.xml +++ b/compatibility/slimefun/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/superiorskyblock/pom.xml b/compatibility/superiorskyblock/pom.xml index 42f26f0588..ac2838d7da 100644 --- a/compatibility/superiorskyblock/pom.xml +++ b/compatibility/superiorskyblock/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/towny/pom.xml b/compatibility/towny/pom.xml index 156be005fe..34a2c92218 100644 --- a/compatibility/towny/pom.xml +++ b/compatibility/towny/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/ultimateclaims/pom.xml b/compatibility/ultimateclaims/pom.xml index 6e5eeb3946..a3491cf651 100644 --- a/compatibility/ultimateclaims/pom.xml +++ b/compatibility/ultimateclaims/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/velocity/pom.xml b/compatibility/velocity/pom.xml index 5d05e2e8c6..b3290b3f23 100644 --- a/compatibility/velocity/pom.xml +++ b/compatibility/velocity/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/voidchest/pom.xml b/compatibility/voidchest/pom.xml index 51f0f70f61..0ea4b1e215 100644 --- a/compatibility/voidchest/pom.xml +++ b/compatibility/voidchest/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/worldedit/pom.xml b/compatibility/worldedit/pom.xml index b963a5f9a3..8d0e43b976 100644 --- a/compatibility/worldedit/pom.xml +++ b/compatibility/worldedit/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/compatibility/worldguard/pom.xml b/compatibility/worldguard/pom.xml index 1221f03f9d..d292afb03b 100644 --- a/compatibility/worldguard/pom.xml +++ b/compatibility/worldguard/pom.xml @@ -7,7 +7,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml com.ghostchu.quickshop.compatibility diff --git a/downloads.json b/downloads.json index b85380dbcf..e63d4fa0f2 100644 --- a/downloads.json +++ b/downloads.json @@ -10,6 +10,11 @@ "snapshots": [ { + "version": "6.2.0.11-SNAPSHOT-13", + "downloadUrl": "https://cdn.discordapp.com/attachments/1457411929550229557/1457411929982107802/QuickShop-Hikari-6.2.0.11-SNAPSHOT-13.jar?ex=695be821&is=695a96a1&hm=a78b56041d7ee2efdfdf44ad9b0f25109f378878cd973f75a57fae53158358d7&" + }, + { + "version": "6.2.0.11-SNAPSHOT-12", "downloadUrl": "https://cdn.discordapp.com/attachments/1447773525803925614/1447774048154292264/QuickShop-Hikari-6.2.0.11-SNAPSHOT-12.jar?ex=6938d827&is=693786a7&hm=e83923c019d519526ab82ceab7b576af8c66093f18893a56a3fb58ca70b0fef8&" }, diff --git a/platform/quickshop-platform-interface/pom.xml b/platform/quickshop-platform-interface/pom.xml index f096525fa3..a6fab20d38 100644 --- a/platform/quickshop-platform-interface/pom.xml +++ b/platform/quickshop-platform-interface/pom.xml @@ -8,7 +8,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml diff --git a/platform/quickshop-platform-paper/pom.xml b/platform/quickshop-platform-paper/pom.xml index 21478728e6..453608dff1 100644 --- a/platform/quickshop-platform-paper/pom.xml +++ b/platform/quickshop-platform-paper/pom.xml @@ -8,7 +8,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 ../../pom.xml diff --git a/pom.xml b/pom.xml index f064fd6c3d..b7bbf41832 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 pom quickshop-hikari Another QuickShop fork diff --git a/quickshop-api/pom.xml b/quickshop-api/pom.xml index 4d99ea84bb..2cb1997e3b 100644 --- a/quickshop-api/pom.xml +++ b/quickshop-api/pom.xml @@ -5,7 +5,7 @@ quickshop-hikari com.ghostchu - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 4.0.0 diff --git a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java index d9ca170115..4b25971f37 100644 --- a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java +++ b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/config/QSConfig.java @@ -19,6 +19,9 @@ */ import com.ghostchu.quickshop.api.QuickShopAPI; +import com.ghostchu.simplereloadlib.ReloadResult; +import com.ghostchu.simplereloadlib.ReloadStatus; +import com.ghostchu.simplereloadlib.Reloadable; import dev.dejvokep.boostedyaml.YamlDocument; import dev.dejvokep.boostedyaml.settings.Settings; import org.jetbrains.annotations.NotNull; @@ -39,7 +42,7 @@ * @author creatorfromhell * @since 6.2.0.11 */ -public class QSConfig { +public class QSConfig implements Reloadable { protected final String fileName; protected final File file; @@ -131,4 +134,14 @@ public void setComment(final String route, final String comment) { return null; } } + + @Override + public ReloadResult reloadModule() throws Exception { + + load(); + + QuickShopAPI.getPluginInstance().getLogger().info("Configuration reloaded successfully. File Name:" + fileName); + + return ReloadResult.builder().status(ReloadStatus.SUCCESS).build(); + } } \ No newline at end of file diff --git a/quickshop-bukkit/pom.xml b/quickshop-bukkit/pom.xml index d66c3ab818..c410c0f0fa 100644 --- a/quickshop-bukkit/pom.xml +++ b/quickshop-bukkit/pom.xml @@ -8,7 +8,7 @@ com.ghostchu quickshop-hikari - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java index 168ad78b14..e3a10599b9 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java @@ -470,7 +470,7 @@ private void initConfiguration() { //noinspection ResultOfMethodCallIgnored javaPlugin.getDataFolder().mkdirs(); - this.config = new MainConfig(); + this.config = new MainConfig(this); if(!this.config.load()) { logger.error("Failed to load config.yml, The binary file of QuickShop may be corrupted. Please re-download from our website."); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java index ccfa76ab70..b1687fe077 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/GuiConfig.java @@ -18,20 +18,20 @@ */ import com.ghostchu.quickshop.QuickShop; +import com.ghostchu.quickshop.api.config.QSConfig; import com.ghostchu.quickshop.util.logger.Log; import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.ReloadStatus; -import com.ghostchu.simplereloadlib.Reloadable; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; +import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; +import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,15 +42,22 @@ * @author creatorfromhell * @since 6.2.0.11 */ -public class GuiConfig implements Reloadable { +public class GuiConfig extends QSConfig { private final QuickShop plugin; + private static GuiConfig instance; private final Map menuConfigs = new HashMap<>(); - private FileConfiguration config; public GuiConfig(@NotNull final QuickShop plugin) { + super("gui.yml", "gui.yml", Collections.emptyList(), + LoaderSettings.builder().setAutoUpdate(true).build(), + UpdaterSettings.builder().setAutoSave(true) + .setVersioning(new BasicVersioning("version")).build()); + + instance = this; this.plugin = plugin; + loadConfig(); plugin.getReloadManager().register(this); } @@ -58,18 +65,10 @@ public GuiConfig(@NotNull final QuickShop plugin) { public void loadConfig() { Log.debug("GUI Config Loading."); - - final File configFile = new File(plugin.getDataFolder(), "gui.yml"); - if(!configFile.exists()) { - try { - Files.copy(plugin.getJavaPlugin().getResource("gui.yml"), configFile.toPath()); - } catch(final IOException e) { - plugin.logger().warn("Failed to copy gui.yml to plugin folder!", e); - } - } - config = YamlConfiguration.loadConfiguration(configFile); menuConfigs.clear(); + this.load(); + // Load menu configurations loadMenuConfig("trade"); loadMenuConfig("keeper"); @@ -82,7 +81,7 @@ public void loadConfig() { private void loadMenuConfig(final String menuName) { - final ConfigurationSection section = config.getConfigurationSection(menuName); + final Section section = this.yaml.getSection(menuName); if(section != null) { menuConfigs.put(menuName, new MenuConfig(section)); } @@ -94,10 +93,9 @@ public MenuConfig getMenuConfig(final String menuName) { return menuConfigs.get(menuName); } - @NotNull - public FileConfiguration getConfig() { + public static YamlDocument yaml() { - return config; + return instance.getYaml(); } /** @@ -111,7 +109,7 @@ public FileConfiguration getConfig() { @NotNull public String getMessage(@NotNull final String path, @NotNull final String defaultValue) { - return config.getString("messages." + path, defaultValue); + return yaml.getString("messages." + path, defaultValue); } /** @@ -124,7 +122,7 @@ public String getMessage(@NotNull final String path, @NotNull final String defau @NotNull public String getMessage(@NotNull final String path) { - return config.getString("messages." + path, path); + return yaml.getString("messages." + path, path); } @Override @@ -141,10 +139,10 @@ public ReloadResult reloadModule() throws Exception { */ public static class MenuConfig { - private final ConfigurationSection section; + private final Section section; private final Map icons = new HashMap<>(); - public MenuConfig(final ConfigurationSection section) { + public MenuConfig(final Section section) { this.section = section; loadIcons(); @@ -152,11 +150,13 @@ public MenuConfig(final ConfigurationSection section) { private void loadIcons() { - for(final String key : section.getKeys(false)) { + for(final Object keyObj : section.getKeys()) { + + final String key = String.valueOf(keyObj); if(key.equals("title") || key.equals("rows")) continue; final Object value = section.get(key); - if(value instanceof final ConfigurationSection iconSection) { + if(value instanceof final Section iconSection) { icons.put(key, new IconConfig(iconSection)); } } @@ -179,99 +179,99 @@ public IconConfig getIcon(final String iconName) { } @NotNull - public ConfigurationSection getSection() { + public Section getSection() { return section; } } /** - * Represents configuration for a single icon/button - */ - public record IconConfig(ConfigurationSection section) { + * Represents configuration for a single icon/button + */ + public record IconConfig(Section section) { @NotNull - public String getMaterial() { - - return section.getString("material", "STONE"); - } + public String getMaterial() { - @Nullable - public String getName() { + return section.getString("material", "STONE"); + } - return section.getString("name"); - } + @Nullable + public String getName() { - @NotNull - public List getLore() { + return section.getString("name"); + } - final Object loreObj = section.get("lore"); - if(loreObj instanceof List) { - final List result = new ArrayList<>(); - for(final Object item : (List)loreObj) { - if(item != null) { - result.add(item.toString()); - } + @NotNull + public List getLore() { + + final Object loreObj = section.get("lore"); + if(loreObj instanceof List) { + final List result = new ArrayList<>(); + for(final Object item : (List)loreObj) { + if(item != null) { + result.add(item.toString()); } - return result; - } else if(loreObj instanceof String) { - final List result = new ArrayList<>(); - result.add((String)loreObj); - return result; } - return new ArrayList<>(); + return result; + } else if(loreObj instanceof String) { + final List result = new ArrayList<>(); + result.add((String)loreObj); + return result; } + return new ArrayList<>(); + } - public int getCustomModelData() { + public int getCustomModelData() { - return section.getInt("custom-model-data", 0); - } + return section.getInt("custom-model-data", 0); + } - public int getSlot() { + public int getSlot() { - return section.getInt("slot", 0); - } + return section.getInt("slot", 0); + } - public int getAmount() { + public int getAmount() { - return section.getInt("amount", 1); - } + return section.getInt("amount", 1); + } - @NotNull - public List getSlots() { + @NotNull + public List getSlots() { - return section.getIntegerList("slots"); - } + return section.getIntList("slots"); + } - @NotNull - public List getQuantities() { + @NotNull + public List getQuantities() { - return section.getIntegerList("quantities"); - } + return section.getIntList("quantities"); + } - @NotNull - public List getRows() { + @NotNull + public List getRows() { - return section.getIntegerList("rows"); - } + return section.getIntList("rows"); + } - public boolean hasCustomModelData() { + public boolean hasCustomModelData() { - return section.contains("custom-model-data") && getCustomModelData() != 0; - } + return section.contains("custom-model-data") && getCustomModelData() != 0; + } - @Override - @Nullable - public ConfigurationSection section() { + @Override + @Nullable + public Section section() { - return section; - } + return section; + } - @Nullable - public IconConfig getSubIcon(final String name) { + @Nullable + public IconConfig getSubIcon(final String name) { - final ConfigurationSection sub = section.getConfigurationSection(name); - return sub != null? new IconConfig(sub) : null; - } + final Section sub = section.getSection(name); + return sub != null? new IconConfig(sub) : null; } + } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java index dc7dbb7296..80132149fe 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/config/MainConfig.java @@ -18,6 +18,7 @@ * along with this program. If not, see . */ +import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.api.config.QSConfig; import dev.dejvokep.boostedyaml.YamlDocument; import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; @@ -36,15 +37,18 @@ public class MainConfig extends QSConfig { private static MainConfig instance; - public MainConfig() { + public MainConfig(final QuickShop plugin) { super("config.yml", "config.yml", Collections.emptyList(), LoaderSettings.builder().setAutoUpdate(true).build(), UpdaterSettings.builder().setAutoSave(true) .setVersioning(new BasicVersioning("config-version")) - .addIgnoredRoute("1033", "limits.ranks", '.').build()); + .addIgnoredRoute("1032", "limits.ranks", '.') + .addIgnoredRoute("1033", "limits.ranks", '.') + .addIgnoredRoute("1034", "limits.ranks", '.').build()); instance = this; + plugin.getReloadManager().register(this); } public static YamlDocument yaml() { diff --git a/quickshop-common/pom.xml b/quickshop-common/pom.xml index 6eda71c9f3..61156c30fe 100644 --- a/quickshop-common/pom.xml +++ b/quickshop-common/pom.xml @@ -5,7 +5,7 @@ quickshop-hikari com.ghostchu - 6.2.0.11-SNAPSHOT-12 + 6.2.0.11-SNAPSHOT-13 From 24a325e787652c1c245acc84dff787a0b6aaec72 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sun, 4 Jan 2026 22:40:14 -0500 Subject: [PATCH 16/19] Remove unused imports and clean up commented-out legacy code across multiple classes for better readability and maintainability. --- .../quickshop/addon/squaremap/Main.java | 8 ++-- .../addon/squaremap/ShopLayerProvider.java | 6 +-- .../com/ghostchu/quickshop/QuickShop.java | 2 +- .../quickshop/database/HikariUtil.java | 1 - .../quickshop/menu/ShopBrowseMenu.java | 2 +- .../quickshop/menu/trade/MainPage.java | 2 +- .../quickshop/shop/ContainerShop.java | 45 ------------------- .../com/ghostchu/quickshop/util/Util.java | 1 - .../item/QuickShopItemMatcherImpl.java | 1 - .../util/privacy/PrivacyController.java | 1 - 10 files changed, 10 insertions(+), 59 deletions(-) diff --git a/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/Main.java b/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/Main.java index 0e8431a0cd..4956149de9 100644 --- a/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/Main.java +++ b/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/Main.java @@ -1,10 +1,6 @@ package com.ghostchu.quickshop.addon.squaremap; import com.ghostchu.quickshop.QuickShop; -import xyz.jpenilla.squaremap.api.BukkitAdapter; -import xyz.jpenilla.squaremap.api.Key; -import xyz.jpenilla.squaremap.api.Squaremap; -import xyz.jpenilla.squaremap.api.SquaremapProvider; import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; @@ -14,6 +10,10 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import xyz.jpenilla.squaremap.api.BukkitAdapter; +import xyz.jpenilla.squaremap.api.Key; +import xyz.jpenilla.squaremap.api.Squaremap; +import xyz.jpenilla.squaremap.api.SquaremapProvider; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; diff --git a/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/ShopLayerProvider.java b/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/ShopLayerProvider.java index 61bb7fdcd2..501fe8b00c 100644 --- a/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/ShopLayerProvider.java +++ b/addon/squaremap/src/main/java/com/ghostchu/quickshop/addon/squaremap/ShopLayerProvider.java @@ -5,15 +5,15 @@ import lombok.Getter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; import xyz.jpenilla.squaremap.api.Key; import xyz.jpenilla.squaremap.api.Point; import xyz.jpenilla.squaremap.api.SimpleLayerProvider; import xyz.jpenilla.squaremap.api.marker.Icon; import xyz.jpenilla.squaremap.api.marker.Marker; import xyz.jpenilla.squaremap.api.marker.MarkerOptions; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Map; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java index e3a10599b9..87214a0093 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java @@ -32,6 +32,7 @@ import com.ghostchu.quickshop.common.util.JsonUtil; import com.ghostchu.quickshop.common.util.QuickExecutor; import com.ghostchu.quickshop.config.GuiConfig; +import com.ghostchu.quickshop.config.MainConfig; import com.ghostchu.quickshop.database.DatabaseIOUtil; import com.ghostchu.quickshop.database.HikariUtil; import com.ghostchu.quickshop.database.SimpleDatabaseHelperV2; @@ -55,7 +56,6 @@ import com.ghostchu.quickshop.menu.ShopKeeperMenu; import com.ghostchu.quickshop.menu.ShopStaffMenu; import com.ghostchu.quickshop.menu.ShopTradeMenu; -import com.ghostchu.quickshop.config.MainConfig; import com.ghostchu.quickshop.metric.MetricListener; import com.ghostchu.quickshop.papi.QuickShopPAPI; import com.ghostchu.quickshop.permission.PermissionManager; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java index 6c2de350c8..7a822da77e 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/database/HikariUtil.java @@ -3,7 +3,6 @@ import com.ghostchu.quickshop.QuickShop; import com.ghostchu.quickshop.util.logger.Log; import dev.dejvokep.boostedyaml.block.implementation.Section; -import org.bukkit.configuration.ConfigurationSection; public class HikariUtil { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java index c79d22df31..21580cd7a4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/ShopBrowseMenu.java @@ -18,9 +18,9 @@ */ import com.ghostchu.quickshop.QuickShop; +import com.ghostchu.quickshop.config.GuiConfig; import com.ghostchu.quickshop.menu.browse.GroupedItemPage; import com.ghostchu.quickshop.menu.browse.ShopListPage; -import com.ghostchu.quickshop.config.GuiConfig; import net.tnemc.menu.core.Menu; import net.tnemc.menu.core.Page; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java index c76de83717..9c3e20096b 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/trade/MainPage.java @@ -22,8 +22,8 @@ import com.ghostchu.quickshop.api.shop.Info; import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.api.shop.ShopAction; -import com.ghostchu.quickshop.menu.browse.MarketUtils; import com.ghostchu.quickshop.config.GuiConfig; +import com.ghostchu.quickshop.menu.browse.MarketUtils; import com.ghostchu.quickshop.menu.shared.GuiChatAction; import com.ghostchu.quickshop.menu.shared.PageSwitchWithCloseAction; import com.ghostchu.quickshop.menu.shared.QuickShopPage; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java index a2e87652ac..29972ca31c 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java @@ -85,9 +85,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import static com.ghostchu.quickshop.util.Util.waitForFuture; @@ -859,49 +857,6 @@ public List getSignText(@NotNull final ProxiedLocale locale) { event.callEvent(); return event.updated(); - -// //Line 1 -// final String headerKey = inventoryAvailable()? "signs.header-available" : "signs.header-unavailable"; -// lines.add(plugin.text().of(headerKey, this.ownerName(false, locale)).forLocale(locale.getLocale())); -// //Line 2 -// final String tradingStringKey = (isStackingShop()? shopType().stackTradingTranslationKey() : shopType().tradingTranslationKey()); -// final String noRemainingStringKey = shopType.outOfStockTranslationKey(); -// final int shopRemaining = shopType().remainingStock(this); -// -// -// final Component line2 = switch(shopRemaining) { -// //Unlimited -// case -1 -> -// plugin.text().of(tradingStringKey, plugin.text().of("signs.unlimited").forLocale(locale.getLocale())).forLocale(locale.getLocale()); -// //No remaining -// case 0 -> plugin.text().of(noRemainingStringKey).forLocale(locale.getLocale()); -// //Has remaining -// default -> -// plugin.text().of(tradingStringKey, Component.text(shopRemaining)).forLocale(locale.getLocale()); -// }; -// lines.add(line2); -// -// //line 3 -// if(plugin.getConfig().getBoolean("shop.force-use-item-original-name") || !this.getItem().hasItemMeta() || !this.getItem().getItemMeta().hasDisplayName()) { -// final Component left = plugin.text().of("signs.item-left").forLocale(locale.getLocale()); -// final Component right = plugin.text().of("signs.item-right").forLocale(locale.getLocale()); -// final Component itemName = Util.getItemStackName(getItem()); -// lines.add(left.append(itemName).append(right)); -// } else { -// lines.add(plugin.text().of("signs.item-left").forLocale(locale.getLocale()).append(Util.getItemStackName(getItem()).append(plugin.text().of("signs.item-right").forLocale(locale.getLocale())))); -// } -// -// //line 4 -// final Component line4; -// if(this.isStackingShop()) { -// line4 = plugin.text().of("signs.stack-price", -// plugin.getShopManager().format(this.getPrice(), this), -// item.getAmount(), -// Util.getItemStackName(item)).forLocale(locale.getLocale()); -// } else { -// line4 = plugin.text().of("signs.price", plugin.getShopManager().format(this.getPrice(), this)).forLocale(locale.getLocale()); -// } -// lines.add(line4); } /** diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java index a3bc511ad5..314019e802 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java @@ -70,7 +70,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java index ff98735610..2b1e33953f 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/matcher/item/QuickShopItemMatcherImpl.java @@ -11,7 +11,6 @@ import org.bukkit.OfflinePlayer; import org.bukkit.attribute.Attribute; import org.bukkit.block.ShulkerBox; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BannerMeta; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java index d8ee5de1a8..c58a8daf9a 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/privacy/PrivacyController.java @@ -4,7 +4,6 @@ import com.ghostchu.quickshop.util.logger.Log; import com.ghostchu.quickshop.util.metric.MetricDataType; import dev.dejvokep.boostedyaml.block.implementation.Section; -import org.bukkit.configuration.ConfigurationSection; import java.util.UUID; From e52ae4ddbad6db10a6bf2d05dbdc68ee75cbb8c0 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sun, 4 Jan 2026 22:45:56 -0500 Subject: [PATCH 17/19] Remove `PaperLib` dependency: replace `PaperLib` methods with native Bukkit implementations for teleportation and block state handling. Clean up unused imports. --- .../quickshop/command/subcommand/SubCommand_Find.java | 10 ++++++---- .../ghostchu/quickshop/menu/browse/ShopListPage.java | 7 ++++--- .../ghostchu/quickshop/shop/AbstractShopManager.java | 3 +-- .../com/ghostchu/quickshop/shop/ContainerShop.java | 3 +-- .../main/java/com/ghostchu/quickshop/util/Util.java | 3 +-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Find.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Find.java index d0942ce9b3..1d2478835e 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Find.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Find.java @@ -8,7 +8,6 @@ import com.ghostchu.quickshop.api.shop.permission.BuiltInShopPermission; import com.ghostchu.quickshop.util.MsgUtil; import com.ghostchu.quickshop.util.Util; -import io.papermc.lib.PaperLib; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.ChatColor; @@ -120,10 +119,11 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman //Function if(usingOldLogic) { + final Map.Entry closest = sortedShops.getFirst(); final Location lookAt = closest.getKey().getLocation().clone().add(0.5, 0.5, 0.5); - PaperLib.teleportAsync(sender, Util.lookAt(sender.getEyeLocation(), lookAt).add(0, -1.62, 0), - PlayerTeleportEvent.TeleportCause.UNKNOWN); + sender.teleportAsync(Util.lookAt(sender.getEyeLocation(), lookAt).add(0, -1.62, 0), PlayerTeleportEvent.TeleportCause.UNKNOWN); + plugin.text().of(sender, "nearby-shop-this-way", closest.getValue().intValue()).send(); } else { plugin.text().of(sender, "nearby-shop-header", lookFor).send(); @@ -132,10 +132,12 @@ public void onCommand(@NotNull final Player sender, @NotNull final String comman final Shop shop = shopDoubleEntry.getKey(); final Location location = shop.getLocation(); ItemStack previewItemStack = shop.getItem().clone(); + final ItemPreviewComponentPrePopulateEvent previewComponentPrePopulateEvent = new ItemPreviewComponentPrePopulateEvent(previewItemStack, sender); + previewComponentPrePopulateEvent.callEvent(); previewItemStack = previewComponentPrePopulateEvent.getItemStack(); - // "nearby-shop-entry": "&a- Info:{0} &aPrice:&b{1} &ax:&b{2} &ay:&b{3} &az:&b{4} &adistance: &b{5} &ablock(s)" + Component entryComponent = plugin.text().of(sender, "nearby-shop-entry", shop.getSignText(plugin.text().findRelativeLanguages(sender)).get(1), shop.getSignText(plugin.text().findRelativeLanguages(sender)).get(3), diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java index fc2e06c297..5bfa14e09a 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/menu/browse/ShopListPage.java @@ -21,7 +21,6 @@ import com.ghostchu.quickshop.api.shop.Shop; import com.ghostchu.quickshop.common.util.CommonUtil; import com.ghostchu.quickshop.config.GuiConfig; -import io.papermc.lib.PaperLib; import net.kyori.adventure.text.Component; import net.tnemc.item.AbstractItemStack; import net.tnemc.item.bukkit.BukkitItemStack; @@ -290,9 +289,11 @@ public void handle(final PageOpenCallback callback) { // Calculate direction to look at shop final double dx = shopLoc.getX() - teleportLoc.getX(); final double dz = shopLoc.getZ() - teleportLoc.getZ(); + teleportLoc.setYaw((float)Math.toDegrees(Math.atan2(-dx, dz))); - teleportLoc.setPitch(30); // Slightly looking down - PaperLib.teleportAsync(p, teleportLoc, PlayerTeleportEvent.TeleportCause.PLUGIN); + teleportLoc.setPitch(30); + + p.teleportAsync(teleportLoc, PlayerTeleportEvent.TeleportCause.PLUGIN); } })); } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/AbstractShopManager.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/AbstractShopManager.java index 3556e0d249..cc2269c0b7 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/AbstractShopManager.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/AbstractShopManager.java @@ -22,7 +22,6 @@ import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import io.papermc.lib.PaperLib; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; @@ -435,7 +434,7 @@ public Shop findShopIncludeAttached(@NotNull final Location loc, final boolean f } } else { // optimize for performance - final BlockState state = PaperLib.getBlockState(currentBlock, false).getState(); + final BlockState state = currentBlock.getState(false); if(!(state instanceof InventoryHolder)) { return null; } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java index 29972ca31c..0b9594ddf4 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/shop/ContainerShop.java @@ -48,7 +48,6 @@ import com.ghostchu.quickshop.util.performance.PerfMonitor; import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.Reloadable; -import io.papermc.lib.PaperLib; import lombok.EqualsAndHashCode; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -881,7 +880,7 @@ public List getSignText(@NotNull final ProxiedLocale locale) { if(b == null) { continue; } - final BlockState state = PaperLib.getBlockState(b, false).getState(); + final BlockState state = b.getState(false); if(!(state instanceof final Sign sign)) { continue; } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java index 314019e802..b8fd0cf929 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/Util.java @@ -17,7 +17,6 @@ import com.ghostchu.quickshop.shop.SimpleInfo; import com.ghostchu.quickshop.shop.display.AbstractDisplayItem; import com.ghostchu.quickshop.util.logger.Log; -import io.papermc.lib.PaperLib; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; @@ -286,7 +285,7 @@ public static boolean canBeShop(@NotNull final Block b) { if(!isShoppables(b.getType())) { return false; } - final BlockState bs = PaperLib.getBlockState(b, false).getState(); + final BlockState bs = b.getState(false); final boolean container = bs instanceof InventoryHolder; if(!container) { if(Util.isDevMode()) { From 176546c4b44fc79492dd0b6234cc3e02ae073dfc Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Sun, 4 Jan 2026 22:52:42 -0500 Subject: [PATCH 18/19] Update changelog: replace `PaperLib` calls with native `PaperAPI` calls --- .changelog/6.2.0.11.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changelog/6.2.0.11.md b/.changelog/6.2.0.11.md index 9531026a62..fc73e6e209 100644 --- a/.changelog/6.2.0.11.md +++ b/.changelog/6.2.0.11.md @@ -101,6 +101,7 @@ - Updated config system to use Boosted-yaml - this allows for auto-updating of config files without complex update scripts - this also allows for the config system to not be minecraft-dependent. +- Replaced logical PaperLib calls with native PaperAPI calls. ## Minor Changes - Bump Java version up to 21 From 982a40d7c115bfc38544dc33703a376e87e88568 Mon Sep 17 00:00:00 2001 From: "Daniel V." Date: Tue, 6 Jan 2026 08:08:57 -0500 Subject: [PATCH 19/19] Introduce `Locatable` interface, implement in `Shop` API, and improve exception handling with `final` keyword across multiple classes. Add `onFolia` flag and related detection to `QuickShop`. Style and minor code consistency updates. --- .../compatibility/CompatibilityModule.java | 4 +- .../quickshop/api/shop/Locatable.java | 36 ++++++++++ .../com/ghostchu/quickshop/api/shop/Shop.java | 13 +--- .../com/ghostchu/quickshop/QuickShop.java | 8 +++ .../command/subcommand/SubCommand_Clean.java | 2 +- .../subcommand/SubCommand_CleanGhost.java | 2 +- .../command/subcommand/SubCommand_Debug.java | 18 ++--- .../quickshop/shop/ContainerShop.java | 10 ++- .../ghostchu/quickshop/shop/ShopPurger.java | 2 +- .../quickshop/shop/history/ShopHistory.java | 72 +++++++++---------- .../util/performance/BatchBukkitExecutor.java | 4 +- 11 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Locatable.java diff --git a/compatibility/common/src/main/java/com/ghostchu/quickshop/compatibility/CompatibilityModule.java b/compatibility/common/src/main/java/com/ghostchu/quickshop/compatibility/CompatibilityModule.java index b0fd5a211d..ee658060d1 100644 --- a/compatibility/common/src/main/java/com/ghostchu/quickshop/compatibility/CompatibilityModule.java +++ b/compatibility/common/src/main/java/com/ghostchu/quickshop/compatibility/CompatibilityModule.java @@ -66,7 +66,7 @@ public void onLoad() { try { saveDefaultConfig(); - } catch(IllegalArgumentException ignored) { + } catch(final IllegalArgumentException ignored) { } this.api = QuickShopAPI.getInstance(); getLogger().info("Loading up..."); @@ -84,7 +84,7 @@ public void onEnable() { // Plugin startup logic try { saveDefaultConfig(); - } catch(IllegalArgumentException ignored) { + } catch(final IllegalArgumentException ignored) { } this.api = QuickShopAPI.getInstance(); Bukkit.getPluginManager().registerEvents(this, this); diff --git a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Locatable.java b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Locatable.java new file mode 100644 index 0000000000..46ef11c79a --- /dev/null +++ b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Locatable.java @@ -0,0 +1,36 @@ +package com.ghostchu.quickshop.api.shop; + + +/* + * QuickShop-Hikari + * Copyright (C) 2025 Daniel "creatorfromhell" Vidmar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Locatable + * + * @author creatorfromhell + * @since 6.2.0.11 + */ +public interface Locatable { + + /** + * Retrieves the location associated with this object. + * + * @return the location of type T associated with this object. + */ + T getLocation(); +} \ No newline at end of file diff --git a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Shop.java b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Shop.java index 4fa86c68d4..445223e6bb 100644 --- a/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Shop.java +++ b/quickshop-api/src/main/java/com/ghostchu/quickshop/api/shop/Shop.java @@ -29,7 +29,7 @@ /** * A shop */ -public interface Shop { +public interface Shop extends Locatable { NamespacedKey SHOP_NAMESPACED_KEY = new NamespacedKey(QuickShopAPI.getPluginInstance(), "shopsign"); @@ -130,14 +130,6 @@ public interface Shop { */ void setItem(@NotNull ItemStack item); - /** - * Get shop's location - * - * @return Shop's location - */ - @NotNull - Location getLocation(); - /** * Get shop's owner QUser @@ -678,5 +670,4 @@ default List getSignText(@NotNull final ProxiedLocale locale) { * Sets the benefit in this shop */ void setShopBenefit(@NotNull BenefitProvider benefit); - -} +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java index 87214a0093..cc809791ca 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/QuickShop.java @@ -255,6 +255,8 @@ public class QuickShop implements QuickShopAPI, Reloadable { @Getter private LogWatcher logWatcher; + private boolean onFolia = false; + /** * The plugin PlaceHolderAPI(null if not present) */ @@ -719,6 +721,7 @@ public final void onEnable() { if(this.folia.isFolia()) { + this.onFolia = true; Bukkit.getPluginManager().registerEvents(new FoliaChatListener(javaPlugin), javaPlugin); Bukkit.getPluginManager().registerEvents(new FoliaInventoryClickListener(javaPlugin), javaPlugin); Bukkit.getPluginManager().registerEvents(new FoliaInventoryCloseListener(javaPlugin), javaPlugin); @@ -1269,6 +1272,11 @@ public Platform platform() { return platform; } + public boolean onFolia() { + + return onFolia; + } + @NotNull public File getDataFolder() { diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Clean.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Clean.java index 196208f647..3df37daddc 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Clean.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Clean.java @@ -46,7 +46,7 @@ public void onCommand(@NotNull final CommandSender sender, @NotNull final String pendingRemoval.add(shop); i++; } - } catch(IllegalStateException e) { + } catch(final IllegalStateException e) { pendingRemoval.add(shop); // The shop is not there anymore, remove it } } diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_CleanGhost.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_CleanGhost.java index fd7d416580..bfe40ae215 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_CleanGhost.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_CleanGhost.java @@ -84,4 +84,4 @@ public void onCommand(@NotNull final CommandSender sender, @NotNull final String } -} +} \ No newline at end of file diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Debug.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Debug.java index e04379539e..7c51f5b1d6 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Debug.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/command/subcommand/SubCommand_Debug.java @@ -87,12 +87,12 @@ private void handleDisplayEntities(final CommandSender sender, final List entities = new ArrayList<>(); for(final World world : Bukkit.getWorlds()) { for(final Entity entity : world.getEntities()) { - if(entity instanceof Item itemEntity) { + if(entity instanceof final Item itemEntity) { if(AbstractDisplayItem.checkIsGuardItemStack(itemEntity.getItemStack())) { entities.add(entity); } } - if(entity instanceof ItemDisplay itemDisplay) { + if(entity instanceof final ItemDisplay itemDisplay) { if(AbstractDisplayItem.checkIsGuardItemStack(itemDisplay.getItemStack())) { entities.add(entity); } @@ -131,7 +131,7 @@ private void handleShopsDirtyAndSave(final CommandSender sender, final List subParams) { - if(!(sender instanceof Player player)) { + if(!(sender instanceof final Player player)) { return; } if(player.getInventory().getItemInMainHand().getType().isAir()) { @@ -164,21 +164,21 @@ private void handleDbConnectionTest(final CommandSender sender, final List{ - try(Connection connection = plugin.getSqlManager().getConnection()) { + try(final Connection connection = plugin.getSqlManager().getConnection()) { if(connection.isValid(1000)) { plugin.text().of(sender, "debug.hikari-cp-working").send(); } else { plugin.text().of(sender, "debug.hikari-cp-not-working"); } - } catch(SQLException e) { + } catch(final SQLException e) { plugin.text().of(sender, "internal-error").send(); e.printStackTrace(); } return null; }).get(5, TimeUnit.SECONDS); - } catch(TimeoutException e) { + } catch(final TimeoutException e) { plugin.text().of(sender, "debug.hikari-cp-timeout").send(); - } catch(ExecutionException | InterruptedException e) { + } catch(final ExecutionException | InterruptedException e) { plugin.text().of(sender, "internal-error").send(); e.printStackTrace(); } @@ -212,7 +212,7 @@ private void handleDumpHikariCPStatus(final CommandSender sender, final List> summaryTopNValuableCustomer String SQL = "SELECT `buyer`, COUNT(`buyer`) AS `count` FROM %s " + "WHERE `time` >= ? AND `time` <= ? AND `shop` IN (" + this.shopIdsPlaceHolders + ") GROUP BY `buyer` ORDER BY `count` DESC LIMIT " + n; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryTopNValuableCustomers"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryTopNValuableCustomers"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { ps.setTimestamp(1, new Timestamp(from.toEpochMilli())); ps.setTimestamp(2, new Timestamp(to.toEpochMilli())); mappingPreparedStatement(ps, 3); @@ -87,7 +87,7 @@ private CompletableFuture> summaryTopNValuableCustomer orderedMap.put(UUID.fromString(set.getString("buyer")), set.getLong("count")); } return orderedMap; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary valuable customers", exception); return orderedMap; } @@ -100,9 +100,9 @@ private CompletableFuture summaryUniquePurchasers(final Instant from, fina String SQL = "SELECT COUNT(DISTINCT `buyer`) FROM %s " + "WHERE `time` >= ? AND `time` <= ? AND `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryUniquePurchasers"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryUniquePurchasers"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { ps.setTimestamp(1, new Timestamp(from.toEpochMilli())); ps.setTimestamp(2, new Timestamp(to.toEpochMilli())); mappingPreparedStatement(ps, 3); @@ -112,7 +112,7 @@ private CompletableFuture summaryUniquePurchasers(final Instant from, fina return set.getLong(1); } return 0L; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0L; } @@ -126,16 +126,16 @@ private CompletableFuture> summaryTopNValuableCustomer String SQL = "SELECT `buyer`, COUNT(`buyer`) AS `count` FROM %s " + "WHERE `shop` IN (" + this.shopIdsPlaceHolders + ") GROUP BY `buyer` ORDER BY `count` DESC LIMIT " + n; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryTopNValuableCustomers"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryTopNValuableCustomers"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { mappingPreparedStatement(ps, 1); @Cleanup final ResultSet set = ps.executeQuery(); while(set.next()) { orderedMap.put(UUID.fromString(set.getString("buyer")), set.getLong("count")); } return orderedMap; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary valuable customers", exception); return orderedMap; } @@ -148,9 +148,9 @@ private CompletableFuture summaryUniquePurchasers() { String SQL = "SELECT COUNT(DISTINCT `buyer`) FROM %s " + "WHERE `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryUniquePurchasers"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryUniquePurchasers"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { perfMonitor.setContext("shopIds=" + shopsMapping.keySet()); mappingPreparedStatement(ps, 1); @Cleanup final ResultSet set = ps.executeQuery(); @@ -158,7 +158,7 @@ private CompletableFuture summaryUniquePurchasers() { return set.getLong(1); } return 0L; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0L; } @@ -171,9 +171,9 @@ private CompletableFuture summaryPurchasesBalance(final Instant from, fi String SQL = "SELECT SUM(`money`) FROM %s " + "WHERE `time` >= ? AND `time` <= ? AND `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesBalance"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesBalance"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { ps.setTimestamp(1, new Timestamp(from.toEpochMilli())); ps.setTimestamp(2, new Timestamp(to.toEpochMilli())); mappingPreparedStatement(ps, 3); @@ -183,7 +183,7 @@ private CompletableFuture summaryPurchasesBalance(final Instant from, fi return set.getDouble(1); } return 0.0d; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0d; } @@ -196,9 +196,9 @@ private CompletableFuture summaryPurchasesBalance() { String SQL = "SELECT SUM(`money`) FROM %s " + "WHERE `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesBalance"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesBalance"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { perfMonitor.setContext("shopIds=" + shopsMapping.keySet()); mappingPreparedStatement(ps, 1); @Cleanup final ResultSet set = ps.executeQuery(); @@ -206,7 +206,7 @@ private CompletableFuture summaryPurchasesBalance() { return set.getDouble(1); } return 0.0d; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0d; } @@ -222,9 +222,9 @@ private CompletableFuture summaryPurchasesCount(final Instant from, final String SQL = "SELECT COUNT(*) FROM %s " + "WHERE `time` >= ? AND `time` <= ? AND `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesCount"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesCount"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { ps.setTimestamp(1, new Timestamp(from.toEpochMilli())); ps.setTimestamp(2, new Timestamp(to.toEpochMilli())); mappingPreparedStatement(ps, 3); @@ -234,7 +234,7 @@ private CompletableFuture summaryPurchasesCount(final Instant from, final return set.getLong(1); } return 0L; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0L; } @@ -247,9 +247,9 @@ private CompletableFuture summaryPurchasesCount() { String SQL = "SELECT COUNT(*) FROM %s " + "WHERE `shop` IN (" + this.shopIdsPlaceHolders + ")"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesCount"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("summaryPurchasesCount"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { mappingPreparedStatement(ps, 1); perfMonitor.setContext("shopIds=" + shopsMapping.keySet()); @Cleanup final ResultSet set = ps.executeQuery(); @@ -257,7 +257,7 @@ private CompletableFuture summaryPurchasesCount() { return set.getLong(1); } return 0L; - } catch(SQLException exception) { + } catch(final SQLException exception) { plugin.logger().warn("Failed to summary unique purchasers", exception); return 0L; } @@ -304,12 +304,12 @@ public List query() throws SQLException { //String SQL = "SELECT * FROM %s WHERE `shop` IN (" + shopIdsPlaceHolders + ") ORDER BY `time` DESC LIMIT " + (page - 1) * pageSize + "," + pageSize; String SQL = "SELECT * FROM %s WHERE `shop` IN (" + shopIdsPlaceHolders + ") ORDER BY `time` DESC"; SQL = String.format(SQL, DataTables.LOG_PURCHASE.getName()); - try(PerfMonitor perfMonitor = new PerfMonitor("historyPageableQuery"); - Connection connection = plugin.getSqlManager().getConnection(); - PreparedStatement ps = connection.prepareStatement(SQL)) { + try(final PerfMonitor perfMonitor = new PerfMonitor("historyPageableQuery"); + final Connection connection = plugin.getSqlManager().getConnection(); + final PreparedStatement ps = connection.prepareStatement(SQL)) { mappingPreparedStatement(ps, 1); perfMonitor.setContext("shopIds=" + shopsMapping.keySet()); - try(ResultSet set = ps.executeQuery()) { + try(final ResultSet set = ps.executeQuery()) { while(set.next()) { if(!isValidSummaryRecordType(set.getString("type"))) { continue; diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/performance/BatchBukkitExecutor.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/performance/BatchBukkitExecutor.java index 6905c1ee40..800b01e662 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/performance/BatchBukkitExecutor.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/performance/BatchBukkitExecutor.java @@ -80,12 +80,14 @@ static class BatchBukkitTask implements Runnable { private final CompletableFuture callback; private WrappedTask task; - public BatchBukkitTask(final Queue tasks, final Consumer consumer, final int maxTickMsUsage, final CompletableFuture callback) { + public BatchBukkitTask(final Queue tasks, final Consumer consumer, final int maxTickMsUsage, + final CompletableFuture callback) { this.tasks = tasks; this.consumer = consumer; this.maxTickMsUsage = maxTickMsUsage; this.callback = callback; + } @Override