33import github .nighter .smartspawner .SmartSpawner ;
44import github .nighter .smartspawner .spawner .gui .main .SpawnerMenuHolder ;
55import github .nighter .smartspawner .spawner .gui .storage .StoragePageHolder ;
6+ import github .nighter .smartspawner .spawner .gui .storage .filter .FilterConfigHolder ;
67import github .nighter .smartspawner .spawner .gui .main .SpawnerMenuUI ;
78import github .nighter .smartspawner .spawner .gui .storage .SpawnerStorageUI ;
89import github .nighter .smartspawner .spawner .gui .layout .GuiLayout ;
@@ -63,6 +64,9 @@ public class SpawnerGuiViewManager implements Listener {
6364 private final Map <UUID , SpawnerViewerInfo > playerToSpawnerMap ;
6465 private final Map <String , Set <UUID >> spawnerToPlayersMap ;
6566 private final Set <Class <? extends InventoryHolder >> validHolderTypes ;
67+
68+ // Additional tracking for filter GUI viewers to prevent duplication exploits
69+ private final Map <String , Set <UUID >> spawnerToFilterViewersMap ;
6670
6771 // NEW: Separate tracking for main menu viewers only (for timer updates)
6872 private final Map <UUID , SpawnerViewerInfo > mainMenuViewers = new ConcurrentHashMap <>();
@@ -102,7 +106,8 @@ private static class SpawnerViewerInfo {
102106 // Enum to track different viewer types
103107 private enum ViewerType {
104108 MAIN_MENU , // SpawnerMenuHolder - needs timer updates
105- STORAGE // StoragePageHolder - no timer updates needed
109+ STORAGE , // StoragePageHolder - no timer updates needed
110+ FILTER // FilterConfigHolder - no timer updates needed
106111 }
107112
108113 public SpawnerGuiViewManager (SmartSpawner plugin ) {
@@ -112,10 +117,12 @@ public SpawnerGuiViewManager(SmartSpawner plugin) {
112117 this .spawnerMenuUI = plugin .getSpawnerMenuUI ();
113118 this .playerToSpawnerMap = new ConcurrentHashMap <>();
114119 this .spawnerToPlayersMap = new ConcurrentHashMap <>();
120+ this .spawnerToFilterViewersMap = new ConcurrentHashMap <>();
115121 this .isTaskRunning = false ;
116122 this .validHolderTypes = Set .of (
117123 SpawnerMenuHolder .class ,
118- StoragePageHolder .class
124+ StoragePageHolder .class ,
125+ FilterConfigHolder .class
119126 );
120127
121128 // Preload commonly used strings to avoid repeated lookups
@@ -257,6 +264,12 @@ public void trackViewer(UUID playerId, SpawnerData spawner, ViewerType viewerTyp
257264 spawnerToMainMenuViewers .computeIfAbsent (spawner .getSpawnerId (), k -> ConcurrentHashMap .newKeySet ())
258265 .add (playerId );
259266 }
267+
268+ // Separately track filter GUI viewers to prevent duplication exploits
269+ if (viewerType == ViewerType .FILTER ) {
270+ spawnerToFilterViewersMap .computeIfAbsent (spawner .getSpawnerId (), k -> ConcurrentHashMap .newKeySet ())
271+ .add (playerId );
272+ }
260273
261274 // Start update task if we have any viewers (for pending updates processing)
262275 if (!isTaskRunning && !playerToSpawnerMap .isEmpty ()) {
@@ -295,6 +308,18 @@ public void untrackViewer(UUID playerId) {
295308 }
296309 }
297310 }
311+
312+ // Also remove from filter viewer tracking if present
313+ if (info != null ) {
314+ String spawnerId = info .spawnerData .getSpawnerId ();
315+ Set <UUID > filterViewers = spawnerToFilterViewersMap .get (spawnerId );
316+ if (filterViewers != null ) {
317+ filterViewers .remove (playerId );
318+ if (filterViewers .isEmpty ()) {
319+ spawnerToFilterViewersMap .remove (spawnerId );
320+ }
321+ }
322+ }
298323
299324 // Also remove from pending updates and performance tracking
300325 pendingUpdates .remove (playerId );
@@ -335,6 +360,7 @@ public void clearAllTrackedGuis() {
335360 spawnerToPlayersMap .clear ();
336361 mainMenuViewers .clear ();
337362 spawnerToMainMenuViewers .clear ();
363+ spawnerToFilterViewersMap .clear ();
338364 pendingUpdates .clear ();
339365 updateFlags .clear ();
340366 lastTimerUpdate .clear ();
@@ -437,6 +463,9 @@ public void onInventoryOpen(InventoryOpenEvent event) {
437463 } else if (holder instanceof StoragePageHolder storageHolder ) {
438464 spawnerData = storageHolder .getSpawnerData ();
439465 viewerType = ViewerType .STORAGE ;
466+ } else if (holder instanceof FilterConfigHolder filterHolder ) {
467+ spawnerData = filterHolder .getSpawnerData ();
468+ viewerType = ViewerType .FILTER ;
440469 }
441470
442471 if (spawnerData != null && viewerType != null ) {
@@ -1347,6 +1376,25 @@ public void closeAllViewersInventory(SpawnerData spawner) {
13471376 }
13481377 }
13491378 }
1379+
1380+ // Force close filter GUI viewers to prevent duplication exploits
1381+ Set <UUID > filterViewers = spawnerToFilterViewersMap .get (spawnerId );
1382+ if (filterViewers != null && !filterViewers .isEmpty ()) {
1383+ // Create a copy to avoid concurrent modification
1384+ Set <UUID > filterViewersCopy = new HashSet <>(filterViewers );
1385+ for (UUID viewerId : filterViewersCopy ) {
1386+ Player viewer = Bukkit .getPlayer (viewerId );
1387+ if (viewer != null && viewer .isOnline ()) {
1388+ // Check if they actually have a filter GUI open for this spawner
1389+ Inventory openInventory = viewer .getOpenInventory ().getTopInventory ();
1390+ if (openInventory != null && openInventory .getHolder (false ) instanceof FilterConfigHolder filterHolder ) {
1391+ if (filterHolder .getSpawnerData ().getSpawnerId ().equals (spawnerId )) {
1392+ viewer .closeInventory ();
1393+ }
1394+ }
1395+ }
1396+ }
1397+ }
13501398
13511399 // Check for stacker viewers if that functionality exists
13521400 if (plugin .getSpawnerStackerHandler () != null ) {
0 commit comments