From 1d5581de2b7fee43babb3b6fb4de10a3d2e9f874 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 06:03:51 +0000 Subject: [PATCH 1/2] Initial plan From dc3cfbb41442a149929b11879ab98b5066cf58c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 06:10:52 +0000 Subject: [PATCH 2/2] Fix duplication bug by tracking and force-closing filter GUI viewers Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../nighter/smartspawner/SmartSpawner.java | 4 ++ .../SpawnerGuiViewManager.java | 52 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java b/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java index 4306de5f..669a98f1 100644 --- a/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java +++ b/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java @@ -371,6 +371,10 @@ public SpawnerStorageUI getSpawnerStorageUI() { return spawnerStorageUI; } + public FilterConfigUI getFilterConfigUI() { + return filterConfigUI; + } + public GuiLayoutConfig getGuiLayoutConfig() { return guiLayoutConfig; } diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/SpawnerGuiViewManager.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/SpawnerGuiViewManager.java index 7cafd2b9..d470c95e 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/SpawnerGuiViewManager.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/SpawnerGuiViewManager.java @@ -3,6 +3,7 @@ import github.nighter.smartspawner.SmartSpawner; import github.nighter.smartspawner.spawner.gui.main.SpawnerMenuHolder; import github.nighter.smartspawner.spawner.gui.storage.StoragePageHolder; +import github.nighter.smartspawner.spawner.gui.storage.filter.FilterConfigHolder; import github.nighter.smartspawner.spawner.gui.main.SpawnerMenuUI; import github.nighter.smartspawner.spawner.gui.storage.SpawnerStorageUI; import github.nighter.smartspawner.spawner.gui.layout.GuiLayout; @@ -63,6 +64,9 @@ public class SpawnerGuiViewManager implements Listener { private final Map playerToSpawnerMap; private final Map> spawnerToPlayersMap; private final Set> validHolderTypes; + + // Additional tracking for filter GUI viewers to prevent duplication exploits + private final Map> spawnerToFilterViewersMap; // NEW: Separate tracking for main menu viewers only (for timer updates) private final Map mainMenuViewers = new ConcurrentHashMap<>(); @@ -102,7 +106,8 @@ private static class SpawnerViewerInfo { // Enum to track different viewer types private enum ViewerType { MAIN_MENU, // SpawnerMenuHolder - needs timer updates - STORAGE // StoragePageHolder - no timer updates needed + STORAGE, // StoragePageHolder - no timer updates needed + FILTER // FilterConfigHolder - no timer updates needed } public SpawnerGuiViewManager(SmartSpawner plugin) { @@ -112,10 +117,12 @@ public SpawnerGuiViewManager(SmartSpawner plugin) { this.spawnerMenuUI = plugin.getSpawnerMenuUI(); this.playerToSpawnerMap = new ConcurrentHashMap<>(); this.spawnerToPlayersMap = new ConcurrentHashMap<>(); + this.spawnerToFilterViewersMap = new ConcurrentHashMap<>(); this.isTaskRunning = false; this.validHolderTypes = Set.of( SpawnerMenuHolder.class, - StoragePageHolder.class + StoragePageHolder.class, + FilterConfigHolder.class ); // Preload commonly used strings to avoid repeated lookups @@ -257,6 +264,12 @@ public void trackViewer(UUID playerId, SpawnerData spawner, ViewerType viewerTyp spawnerToMainMenuViewers.computeIfAbsent(spawner.getSpawnerId(), k -> ConcurrentHashMap.newKeySet()) .add(playerId); } + + // Separately track filter GUI viewers to prevent duplication exploits + if (viewerType == ViewerType.FILTER) { + spawnerToFilterViewersMap.computeIfAbsent(spawner.getSpawnerId(), k -> ConcurrentHashMap.newKeySet()) + .add(playerId); + } // Start update task if we have any viewers (for pending updates processing) if (!isTaskRunning && !playerToSpawnerMap.isEmpty()) { @@ -295,6 +308,18 @@ public void untrackViewer(UUID playerId) { } } } + + // Also remove from filter viewer tracking if present + if (info != null) { + String spawnerId = info.spawnerData.getSpawnerId(); + Set filterViewers = spawnerToFilterViewersMap.get(spawnerId); + if (filterViewers != null) { + filterViewers.remove(playerId); + if (filterViewers.isEmpty()) { + spawnerToFilterViewersMap.remove(spawnerId); + } + } + } // Also remove from pending updates and performance tracking pendingUpdates.remove(playerId); @@ -335,6 +360,7 @@ public void clearAllTrackedGuis() { spawnerToPlayersMap.clear(); mainMenuViewers.clear(); spawnerToMainMenuViewers.clear(); + spawnerToFilterViewersMap.clear(); pendingUpdates.clear(); updateFlags.clear(); lastTimerUpdate.clear(); @@ -437,6 +463,9 @@ public void onInventoryOpen(InventoryOpenEvent event) { } else if (holder instanceof StoragePageHolder storageHolder) { spawnerData = storageHolder.getSpawnerData(); viewerType = ViewerType.STORAGE; + } else if (holder instanceof FilterConfigHolder filterHolder) { + spawnerData = filterHolder.getSpawnerData(); + viewerType = ViewerType.FILTER; } if (spawnerData != null && viewerType != null) { @@ -1347,6 +1376,25 @@ public void closeAllViewersInventory(SpawnerData spawner) { } } } + + // Force close filter GUI viewers to prevent duplication exploits + Set filterViewers = spawnerToFilterViewersMap.get(spawnerId); + if (filterViewers != null && !filterViewers.isEmpty()) { + // Create a copy to avoid concurrent modification + Set filterViewersCopy = new HashSet<>(filterViewers); + for (UUID viewerId : filterViewersCopy) { + Player viewer = Bukkit.getPlayer(viewerId); + if (viewer != null && viewer.isOnline()) { + // Check if they actually have a filter GUI open for this spawner + Inventory openInventory = viewer.getOpenInventory().getTopInventory(); + if (openInventory != null && openInventory.getHolder(false) instanceof FilterConfigHolder filterHolder) { + if (filterHolder.getSpawnerData().getSpawnerId().equals(spawnerId)) { + viewer.closeInventory(); + } + } + } + } + } // Check for stacker viewers if that functionality exists if (plugin.getSpawnerStackerHandler() != null) {