Skip to content

Commit 9fa2396

Browse files
authored
Merge pull request #9 from NighterDevelopment/copilot/fix-f560a800-db4a-4c77-b463-934c32070a77
Fix GUI layout version placement and dynamic slot detection for all GUI buttons
2 parents c7ff1bc + 0166685 commit 9fa2396

7 files changed

Lines changed: 166 additions & 38 deletions

File tree

core/src/main/java/github/nighter/smartspawner/SmartSpawner.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ private void setupBtatsMetrics() {
315315
public void reload() {
316316
// reload gui components
317317
guiLayoutConfig.reloadLayouts();
318+
319+
// Clear spawner info slot cache since layout may have changed
320+
spawnerGuiViewManager.clearSlotCache();
321+
318322
spawnerStorageAction.reload();
319323
spawnerStorageUI.reload();
320324
filterConfigUI.reload();

core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/SpawnerGuiViewManager.java

Lines changed: 141 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import github.nighter.smartspawner.spawner.gui.storage.StoragePageHolder;
66
import github.nighter.smartspawner.spawner.gui.main.SpawnerMenuUI;
77
import github.nighter.smartspawner.spawner.gui.storage.SpawnerStorageUI;
8+
import github.nighter.smartspawner.spawner.gui.layout.GuiLayout;
9+
import github.nighter.smartspawner.spawner.gui.layout.GuiButton;
10+
import github.nighter.smartspawner.spawner.gui.synchronization.ItemUpdater;
811
import github.nighter.smartspawner.spawner.properties.SpawnerData;
912
import github.nighter.smartspawner.language.LanguageManager;
1013
import github.nighter.smartspawner.Scheduler;
@@ -40,10 +43,10 @@ public class SpawnerGuiViewManager implements Listener {
4043
private static final long BATCH_PROCESS_INTERVAL = 5L; // Process batches every 250ms
4144
private static final int MAX_PLAYERS_PER_BATCH = 10; // Limit players processed per batch
4245

43-
// GUI slot constants
44-
private static final int CHEST_SLOT = 11;
45-
private static final int SPAWNER_INFO_SLOT = 13;
46-
private static final int EXP_SLOT = 15;
46+
// Cached slot positions - initialized once when config loads, re-initialized on reload
47+
private volatile int cachedStorageSlot = -1;
48+
private volatile int cachedExpSlot = -1;
49+
private volatile int cachedSpawnerInfoSlot = -1;
4750

4851
// Update flags - using bit flags for efficient state tracking
4952
private static final int UPDATE_CHEST = 1;
@@ -117,6 +120,9 @@ public SpawnerGuiViewManager(SmartSpawner plugin) {
117120

118121
// Preload commonly used strings to avoid repeated lookups
119122
initCachedStrings();
123+
124+
// Initialize all slot positions from layout configuration
125+
initializeSlotPositions();
120126
}
121127

122128
private void initCachedStrings() {
@@ -335,9 +341,84 @@ public void clearAllTrackedGuis() {
335341
lastTimerValue.clear();
336342
}
337343

338-
// ===============================================================
339-
// Event Handlers
340-
// ===============================================================
344+
/**
345+
* Initialize all GUI slot positions from the current layout configuration.
346+
* This is called once during construction and again when layout is reloaded
347+
* for optimal performance.
348+
*/
349+
private void initializeSlotPositions() {
350+
// Get the current layout
351+
GuiLayout layout = plugin.getGuiLayoutConfig().getCurrentMainLayout();
352+
if (layout == null) {
353+
// Set all slots to -1 if no layout is available
354+
cachedStorageSlot = -1;
355+
cachedExpSlot = -1;
356+
cachedSpawnerInfoSlot = -1;
357+
return;
358+
}
359+
360+
// Initialize storage slot
361+
GuiButton storageButton = layout.getButton("storage");
362+
cachedStorageSlot = storageButton != null ? storageButton.getSlot() : -1;
363+
364+
// Initialize exp slot
365+
GuiButton expButton = layout.getButton("exp");
366+
cachedExpSlot = expButton != null ? expButton.getSlot() : -1;
367+
368+
// Initialize spawner info slot using the same logic as SpawnerMenuUI
369+
GuiButton spawnerInfoButton = null;
370+
371+
// Check for shop integration to determine which button to use
372+
if (plugin.hasSellIntegration()) {
373+
spawnerInfoButton = layout.getButton("spawner_info_with_shop");
374+
}
375+
376+
if (spawnerInfoButton == null) {
377+
spawnerInfoButton = layout.getButton("spawner_info_no_shop");
378+
}
379+
380+
if (spawnerInfoButton == null) {
381+
spawnerInfoButton = layout.getButton("spawner_info");
382+
}
383+
384+
cachedSpawnerInfoSlot = spawnerInfoButton != null ? spawnerInfoButton.getSlot() : -1;
385+
}
386+
387+
/**
388+
* Get the storage slot from the cached layout configuration.
389+
*
390+
* @return the slot number for the storage button, or -1 if not found
391+
*/
392+
private int getStorageSlot() {
393+
return cachedStorageSlot;
394+
}
395+
396+
/**
397+
* Get the exp slot from the cached layout configuration.
398+
*
399+
* @return the slot number for the exp button, or -1 if not found
400+
*/
401+
private int getExpSlot() {
402+
return cachedExpSlot;
403+
}
404+
405+
/**
406+
* Get the spawner info slot from the cached layout configuration.
407+
*
408+
* @return the slot number for the spawner info button, or -1 if not found
409+
*/
410+
private int getSpawnerInfoSlot() {
411+
return cachedSpawnerInfoSlot;
412+
}
413+
414+
/**
415+
* Clear all cached slot positions and re-initialize them when GUI layout changes.
416+
* This method is called when layout configuration is reloaded.
417+
*/
418+
public void clearSlotCache() {
419+
// Re-initialize all slot positions from the updated layout
420+
initializeSlotPositions();
421+
}
341422

342423
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
343424
public void onInventoryOpen(InventoryOpenEvent event) {
@@ -518,9 +599,12 @@ private void updateGuiForSpawnerInfo() {
518599
InventoryHolder holder = openInventory.getHolder(false);
519600

520601
if (holder instanceof SpawnerMenuHolder) {
521-
updateSpawnerInfoItemTimerOptimized(openInventory, spawner, finalTimerValue);
522-
// Force inventory update to ensure changes are visible to the player
523-
player.updateInventory();
602+
int spawnerInfoSlot = getSpawnerInfoSlot();
603+
if (spawnerInfoSlot >= 0) {
604+
updateSpawnerInfoItemTimerOptimized(openInventory, spawner, finalTimerValue, spawnerInfoSlot);
605+
// Force inventory update to ensure changes are visible to the player
606+
player.updateInventory();
607+
}
524608
} else {
525609
// Player no longer has main menu open, remove from main menu tracking
526610
untrackViewer(finalPlayerId);
@@ -581,18 +665,27 @@ private void processInventoryUpdate(Player player, Inventory inventory, SpawnerD
581665
boolean needsUpdate = false;
582666

583667
if ((flags & UPDATE_CHEST) != 0) {
584-
updateChestItem(inventory, spawner);
585-
needsUpdate = true;
668+
int storageSlot = getStorageSlot();
669+
if (storageSlot >= 0) {
670+
updateChestItem(inventory, spawner, storageSlot);
671+
needsUpdate = true;
672+
}
586673
}
587674

588675
if ((flags & UPDATE_INFO) != 0) {
589-
updateSpawnerInfoItem(inventory, spawner, player);
590-
needsUpdate = true;
676+
int spawnerInfoSlot = getSpawnerInfoSlot();
677+
if (spawnerInfoSlot >= 0) {
678+
updateSpawnerInfoItem(inventory, spawner, player, spawnerInfoSlot);
679+
needsUpdate = true;
680+
}
591681
}
592682

593683
if ((flags & UPDATE_EXP) != 0) {
594-
updateExpItem(inventory, spawner);
595-
needsUpdate = true;
684+
int expSlot = getExpSlot();
685+
if (expSlot >= 0) {
686+
updateExpItem(inventory, spawner, expSlot);
687+
needsUpdate = true;
688+
}
596689
}
597690

598691
if (needsUpdate) {
@@ -677,7 +770,7 @@ private void updateMainMenuViewers(SpawnerData spawner) {
677770
return;
678771
}
679772

680-
updateSpawnerInfoItemTimerOptimized(openInv, spawner, finalTimerValue);
773+
updateSpawnerInfoItemTimerOptimized(openInv, spawner, finalTimerValue, getSpawnerInfoSlot());
681774
viewer.updateInventory();
682775
});
683776
}
@@ -711,9 +804,12 @@ public void forceTimerUpdate(Player player, SpawnerData spawner) {
711804

712805
InventoryHolder holder = openInventory.getHolder(false);
713806
if (holder instanceof SpawnerMenuHolder) {
714-
updateSpawnerInfoItemTimer(openInventory, spawner);
715-
// Force inventory update to ensure changes are visible immediately
716-
player.updateInventory();
807+
int spawnerInfoSlot = getSpawnerInfoSlot();
808+
if (spawnerInfoSlot >= 0) {
809+
updateSpawnerInfoItemTimer(openInventory, spawner, spawnerInfoSlot);
810+
// Force inventory update to ensure changes are visible immediately
811+
player.updateInventory();
812+
}
717813
}
718814
});
719815
}
@@ -867,9 +963,11 @@ public void updateSpawnerMenuGui(Player player, SpawnerData spawner, boolean for
867963
updateFlags.put(player.getUniqueId(), UPDATE_ALL);
868964
}
869965

870-
private void updateSpawnerInfoItem(Inventory inventory, SpawnerData spawner, Player player) {
966+
private void updateSpawnerInfoItem(Inventory inventory, SpawnerData spawner, Player player, int spawnerInfoSlot) {
967+
if (spawnerInfoSlot < 0) return;
968+
871969
// Get the current spawner info item from the inventory
872-
ItemStack currentSpawnerItem = inventory.getItem(SPAWNER_INFO_SLOT);
970+
ItemStack currentSpawnerItem = inventory.getItem(spawnerInfoSlot);
873971
if (currentSpawnerItem == null || !currentSpawnerItem.hasItemMeta()) return;
874972

875973
// Create a freshly generated spawner info item using the method from SpawnerMenuUI
@@ -881,7 +979,7 @@ private void updateSpawnerInfoItem(Inventory inventory, SpawnerData spawner, Pla
881979
preserveTimerInfo(currentSpawnerItem, newSpawnerItem);
882980

883981
// Update the item in the inventory
884-
inventory.setItem(SPAWNER_INFO_SLOT, newSpawnerItem);
982+
inventory.setItem(spawnerInfoSlot, newSpawnerItem);
885983
}
886984
}
887985

@@ -962,13 +1060,15 @@ private void preserveTimerInfo(ItemStack currentItem, ItemStack newItem) {
9621060
* Optimized version of updateSpawnerInfoItemTimer that accepts pre-calculated timer value
9631061
* to avoid redundant calculations and improve performance.
9641062
*/
965-
private void updateSpawnerInfoItemTimerOptimized(Inventory inventory, SpawnerData spawner, String timeDisplay) {
1063+
private void updateSpawnerInfoItemTimerOptimized(Inventory inventory, SpawnerData spawner, String timeDisplay, int spawnerInfoSlot) {
9661064
// Skip timer updates if GUI doesn't use timer placeholders
9671065
if (!isTimerPlaceholdersEnabled()) {
9681066
return;
9691067
}
9701068

971-
ItemStack spawnerItem = inventory.getItem(SPAWNER_INFO_SLOT);
1069+
if (spawnerInfoSlot < 0) return;
1070+
1071+
ItemStack spawnerItem = inventory.getItem(spawnerInfoSlot);
9721072
if (spawnerItem == null || !spawnerItem.hasItemMeta()) return;
9731073

9741074
ItemMeta meta = spawnerItem.getItemMeta();
@@ -1004,17 +1104,19 @@ private void updateSpawnerInfoItemTimerOptimized(Inventory inventory, SpawnerDat
10041104
meta.setLore(updatedLore);
10051105
spawnerItem.setItemMeta(meta);
10061106
// Update the inventory directly to ensure changes are applied
1007-
inventory.setItem(SPAWNER_INFO_SLOT, spawnerItem);
1107+
inventory.setItem(spawnerInfoSlot, spawnerItem);
10081108
}
10091109
}
10101110

1011-
private void updateSpawnerInfoItemTimer(Inventory inventory, SpawnerData spawner) {
1111+
private void updateSpawnerInfoItemTimer(Inventory inventory, SpawnerData spawner, int spawnerInfoSlot) {
10121112
// Skip timer updates if GUI doesn't use timer placeholders
10131113
if (!isTimerPlaceholdersEnabled()) {
10141114
return;
10151115
}
10161116

1017-
ItemStack spawnerItem = inventory.getItem(SPAWNER_INFO_SLOT);
1117+
if (spawnerInfoSlot < 0) return;
1118+
1119+
ItemStack spawnerItem = inventory.getItem(spawnerInfoSlot);
10181120
if (spawnerItem == null || !spawnerItem.hasItemMeta()) return;
10191121

10201122
ItemMeta meta = spawnerItem.getItemMeta();
@@ -1064,7 +1166,7 @@ private void updateSpawnerInfoItemTimer(Inventory inventory, SpawnerData spawner
10641166
meta.setLore(updatedLore);
10651167
spawnerItem.setItemMeta(meta);
10661168
// Update the inventory directly to ensure changes are applied
1067-
inventory.setItem(SPAWNER_INFO_SLOT, spawnerItem);
1169+
inventory.setItem(spawnerInfoSlot, spawnerItem);
10681170
}
10691171
}
10701172

@@ -1199,31 +1301,35 @@ private String formatTime(long milliseconds) {
11991301
return String.format("%02d:%02d", minutes, seconds);
12001302
}
12011303

1202-
private void updateChestItem(Inventory inventory, SpawnerData spawner) {
1304+
private void updateChestItem(Inventory inventory, SpawnerData spawner, int storageSlot) {
1305+
if (storageSlot < 0) return;
1306+
12031307
// Get the chest item from the inventory
1204-
ItemStack currentChestItem = inventory.getItem(CHEST_SLOT);
1308+
ItemStack currentChestItem = inventory.getItem(storageSlot);
12051309
if (currentChestItem == null || !currentChestItem.hasItemMeta()) return;
12061310

12071311
// Create a freshly generated chest item using the optimized method from SpawnerMenuUI
12081312
ItemStack newChestItem = spawnerMenuUI.createLootStorageItem(spawner);
12091313

12101314
// If the new item is different from current item, update it
12111315
if (!ItemUpdater.areItemsEqual(currentChestItem, newChestItem)) {
1212-
inventory.setItem(CHEST_SLOT, newChestItem);
1316+
inventory.setItem(storageSlot, newChestItem);
12131317
}
12141318
}
12151319

1216-
private void updateExpItem(Inventory inventory, SpawnerData spawner) {
1320+
private void updateExpItem(Inventory inventory, SpawnerData spawner, int expSlot) {
1321+
if (expSlot < 0) return;
1322+
12171323
// Get the exp item from the inventory
1218-
ItemStack currentExpItem = inventory.getItem(EXP_SLOT);
1324+
ItemStack currentExpItem = inventory.getItem(expSlot);
12191325
if (currentExpItem == null || !currentExpItem.hasItemMeta()) return;
12201326

12211327
// Create a freshly generated exp item using the method from SpawnerMenuUI
12221328
ItemStack newExpItem = spawnerMenuUI.createExpItem(spawner);
12231329

12241330
// If the new item is different from current item, update it
12251331
if (!ItemUpdater.areItemsEqual(currentExpItem, newExpItem)) {
1226-
inventory.setItem(EXP_SLOT, newExpItem);
1332+
inventory.setItem(expSlot, newExpItem);
12271333
}
12281334
}
12291335

core/src/main/java/github/nighter/smartspawner/updates/GuiLayoutUpdater.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,19 @@ private void createDefaultLayoutFileWithHeader(String layoutName, File destinati
104104
plugin.saveResource(resourcePath, true);
105105

106106
FileConfiguration config = YamlConfiguration.loadConfiguration(destinationFile);
107-
config.set(GUI_LAYOUT_VERSION_KEY, currentVersion);
108-
config.save(destinationFile);
107+
108+
// Create a new configuration with version at the top
109+
FileConfiguration newConfig = new YamlConfiguration();
110+
newConfig.set(GUI_LAYOUT_VERSION_KEY, currentVersion);
111+
112+
// Copy all existing keys to preserve order, with version first
113+
for (String key : config.getKeys(false)) {
114+
if (!key.equals(GUI_LAYOUT_VERSION_KEY)) {
115+
newConfig.set(key, config.get(key));
116+
}
117+
}
118+
119+
newConfig.save(destinationFile);
109120

110121
} catch (Exception e) {
111122
plugin.getLogger().log(Level.WARNING, "Failed to create default layout file with header for " + layoutName + "/" + fileName, e);

core/src/main/resources/gui_layouts/DonutSMP/main_gui.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
gui_layout_version: 1.5.1
2+
13
# DonutSMP Main GUI Layout Configuration
24
# This configures the main spawner GUI layout (27 slots, 3 rows of 9)
35
# Valid materials can be found at: https://jd.papermc.io/paper/1.21.8/org/bukkit/Material.html

core/src/main/resources/gui_layouts/DonutSMP/storage_gui.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
gui_layout_version: 1.5.1
2+
13
# DonutSMP Storage GUI Layout Configuration
24
# Slot positions: 1-9 corresponds to inventory slots 46-54 (bottom row)
35
# Valid materials can be found at: https://jd.papermc.io/paper/1.21.6/org/bukkit/Material.html
@@ -39,4 +41,3 @@ buttons:
3941
slot: 8
4042
material: DROPPER
4143
enabled: true
42-

core/src/main/resources/gui_layouts/default/main_gui.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
gui_layout_version: 1.5.1
2+
13
# Default Main GUI Layout Configuration
24
# This configures the main spawner GUI layout (27 slots, 3 rows of 9)
35
# Valid materials can be found at: https://jd.papermc.io/paper/1.21.8/org/bukkit/Material.html

core/src/main/resources/gui_layouts/default/storage_gui.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
gui_layout_version: 1.5.1
2+
13
# Default Storage GUI Layout Configuration
24
# Slot positions: 1-9 corresponds to inventory slots 46-54 (bottom row)
35
# Valid materials can be found at: https://jd.papermc.io/paper/1.21.8/org/bukkit/Material.html

0 commit comments

Comments
 (0)