diff --git a/api/src/main/java/github/nighter/smartspawner/api/events/SpawnerOpenGUIEvent.java b/api/src/main/java/github/nighter/smartspawner/api/events/SpawnerOpenGUIEvent.java new file mode 100644 index 00000000..b2c28865 --- /dev/null +++ b/api/src/main/java/github/nighter/smartspawner/api/events/SpawnerOpenGUIEvent.java @@ -0,0 +1,47 @@ +package github.nighter.smartspawner.api.events; + +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class SpawnerOpenGUIEvent extends SpawnerEvent implements Cancellable { + @Getter + private final Player player; + @Getter + private final EntityType entityType; + @Getter + private final boolean isRefresh; + private boolean cancelled = false; + + private static final HandlerList handlers = new HandlerList(); + + public SpawnerOpenGUIEvent(Player player, Location location, EntityType entityType, int stackSize, boolean isRefresh) { + super(location, stackSize); + this.player = player; + this.entityType = entityType; + this.isRefresh = isRefresh; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public static @NotNull HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java b/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java index 9aa562d9..c1a9c1e4 100644 --- a/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java +++ b/core/src/main/java/github/nighter/smartspawner/SmartSpawner.java @@ -321,6 +321,11 @@ public void reload() { // Clear spawner info slot cache since layout may have changed spawnerGuiViewManager.clearSlotCache(); + // Clear GUI item cache since layout/config may have changed + if (spawnerMenuUI != null) { + spawnerMenuUI.clearCache(); + } + spawnerStorageAction.reload(); spawnerStorageUI.reload(); filterConfigUI.reload(); diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/layout/GuiLayoutConfig.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/layout/GuiLayoutConfig.java index 3c3e1743..d4109ae6 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/layout/GuiLayoutConfig.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/layout/GuiLayoutConfig.java @@ -190,9 +190,12 @@ private boolean loadButton(FileConfiguration config, GuiLayout layout, String bu // Load actions Map actions = new HashMap<>(); ConfigurationSection actionsSection = config.getConfigurationSection(path + ".actions"); - if (actionsSection != null) { + if (actionsSection != null && !actionsSection.getKeys(false).isEmpty()) { for (String actionKey : actionsSection.getKeys(false)) { - actions.put(actionKey, actionsSection.getString(actionKey)); + String actionValue = actionsSection.getString(actionKey); + if (actionValue != null && !actionValue.equals("none")) { + actions.put(actionKey, actionValue); + } } } diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuAction.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuAction.java index cc6dd979..d6185436 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuAction.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuAction.java @@ -148,7 +148,7 @@ private boolean handleLayoutAction(Player player, SpawnerData spawner, int slot, spawnerStackerUI.openStackerGui(player, spawner); player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1.0f, 1.0f); return true; - case "sell_inventory": + case "sell_and_exp": if (isClickTooFrequent(player)) { return true; } diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuFormUI.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuFormUI.java index 24583613..48a5c174 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuFormUI.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuFormUI.java @@ -108,14 +108,14 @@ private List collectAvailableButtons(GuiLayout layout, Player player buttons.add(new ButtonInfo("open_stacker", text, "https://static.wikia.nocookie.net/minecraft_gamepedia/images/c/cf/Spawner_with_fire.png/revision/latest?cb=20190925003048")); } - // Check for sell_inventory button (Claim XP and Sell All) + // Check for sell_and_exp button (Claim XP and Sell All) if (hasShopPermission) { GuiButton sellInventoryButton = layout.getButton("spawner_info_with_shop"); if (sellInventoryButton != null && sellInventoryButton.isEnabled() && sellInventoryButton.getAction("left_click") != null && - sellInventoryButton.getAction("left_click").equals("sell_inventory")) { - String text = languageManager.getGuiItemName("bedrock_gui.buttons.sell_inventory", placeholders); - buttons.add(new ButtonInfo("sell_inventory", text, "https://img.icons8.com/?size=100&id=12815&format=png&color=FFD700")); + sellInventoryButton.getAction("left_click").equals("sell_and_exp")) { + String text = languageManager.getGuiItemName("bedrock_gui.buttons.sell_and_exp", placeholders); + buttons.add(new ButtonInfo("sell_and_exp", text, "https://img.icons8.com/?size=100&id=12815&format=png&color=FFD700")); } // Check for sell_all button (Sell items only) @@ -259,7 +259,7 @@ private void handleButtonAction(Player player, SpawnerData spawner, String actio case "open_stacker": handleSpawnerInfo(player, spawner); break; - case "sell_inventory": + case "sell_and_exp": handleSellInventory(player, spawner); break; case "sell_all": diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuUI.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuUI.java index cc80ebc7..08b310cc 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuUI.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/main/SpawnerMenuUI.java @@ -9,6 +9,7 @@ import github.nighter.smartspawner.spawner.properties.SpawnerData; import github.nighter.smartspawner.spawner.properties.VirtualInventory; import github.nighter.smartspawner.language.LanguageManager; +import github.nighter.smartspawner.api.events.SpawnerOpenGUIEvent; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Sound; @@ -37,6 +38,11 @@ public class SpawnerMenuUI { private final String lootItemFormat; private final String emptyLootMessage; + // Cache for GUI items - cleared when spawner data changes + private final Map itemCache = new HashMap<>(); + private final Map cacheTimestamps = new HashMap<>(); + private static final long CACHE_EXPIRY_TIME_MS = 30000; // 30 seconds + public SpawnerMenuUI(SmartSpawner plugin) { this.plugin = plugin; this.languageManager = plugin.getLanguageManager(); @@ -46,7 +52,36 @@ public SpawnerMenuUI(SmartSpawner plugin) { this.emptyLootMessage = languageManager.getGuiItemName(EMPTY_LOOT_MESSAGE_KEY, EMPTY_PLACEHOLDERS); } + public void clearCache() { + itemCache.clear(); + cacheTimestamps.clear(); + } + + public void invalidateSpawnerCache(String spawnerId) { + itemCache.entrySet().removeIf(entry -> entry.getKey().startsWith(spawnerId + "|")); + cacheTimestamps.entrySet().removeIf(entry -> entry.getKey().startsWith(spawnerId + "|")); + } + + private boolean isCacheEntryExpired(String cacheKey) { + Long timestamp = cacheTimestamps.get(cacheKey); + return timestamp == null || System.currentTimeMillis() - timestamp > CACHE_EXPIRY_TIME_MS; + } + public void openSpawnerMenu(Player player, SpawnerData spawner, boolean refresh) { + // Fire SpawnerOpenGUI API event + SpawnerOpenGUIEvent openEvent = new SpawnerOpenGUIEvent( + player, + spawner.getSpawnerLocation(), + spawner.getEntityType(), + spawner.getStackSize(), + refresh + ); + Bukkit.getPluginManager().callEvent(openEvent); + + if (openEvent.isCancelled()) { + return; + } + Inventory menu = createMenu(spawner); GuiLayout layout = plugin.getGuiLayoutConfig().getCurrentMainLayout(); @@ -114,10 +149,19 @@ private Inventory createMenu(SpawnerData spawner) { } public ItemStack createLootStorageItem(SpawnerData spawner) { - // Get important data upfront + // Generate cache key based on spawner state VirtualInventory virtualInventory = spawner.getVirtualInventory(); int currentItems = virtualInventory.getUsedSlots(); int maxSlots = spawner.getMaxSpawnerLootSlots(); + String cacheKey = spawner.getSpawnerId() + "|storage|" + currentItems + "|" + maxSlots + "|" + virtualInventory.hashCode(); + + // Check cache first + ItemStack cachedItem = itemCache.get(cacheKey); + if (cachedItem != null && !isCacheEntryExpired(cacheKey)) { + return cachedItem.clone(); + } + + // Get important data upfront int percentStorage = calculatePercentage(currentItems, maxSlots); // Not in cache, create new item @@ -146,6 +190,10 @@ public ItemStack createLootStorageItem(SpawnerData spawner) { chestMeta.setLore(lore); chestItem.setItemMeta(chestMeta); + // Cache the result + itemCache.put(cacheKey, chestItem.clone()); + cacheTimestamps.put(cacheKey, System.currentTimeMillis()); + return chestItem; } @@ -333,6 +381,12 @@ public ItemStack createExpItem(SpawnerData spawner) { // Create cache key for this specific spawner's exp state String cacheKey = spawner.getSpawnerId() + "|exp|" + currentExp + "|" + maxExp; + // Check cache first + ItemStack cachedItem = itemCache.get(cacheKey); + if (cachedItem != null && !isCacheEntryExpired(cacheKey)) { + return cachedItem.clone(); + } + // Not in cache, create the ItemStack ItemStack expItem = new ItemStack(Material.EXPERIENCE_BOTTLE); ItemMeta expMeta = expItem.getItemMeta(); @@ -357,6 +411,10 @@ public ItemStack createExpItem(SpawnerData spawner) { expItem.setItemMeta(expMeta); + // Cache the result + itemCache.put(cacheKey, expItem.clone()); + cacheTimestamps.put(cacheKey, System.currentTimeMillis()); + return expItem; } 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 d470c95e..3697beb1 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 @@ -857,6 +857,11 @@ public void updateSpawnerMenuViewers(SpawnerData spawner) { Set viewers = spawnerToPlayersMap.get(spawner.getSpawnerId()); if (viewers == null || viewers.isEmpty()) return; + // Invalidate cache for this spawner + if (plugin.getSpawnerMenuUI() != null) { + plugin.getSpawnerMenuUI().invalidateSpawnerCache(spawner.getSpawnerId()); + } + int viewerCount = viewers.size(); if (viewerCount > 10) { plugin.debug(viewerCount + " spawner menu viewers to update for " + spawner.getSpawnerId() + " (batch update)"); diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/properties/SpawnerData.java b/core/src/main/java/github/nighter/smartspawner/spawner/properties/SpawnerData.java index f43a3b6c..5d17eaf2 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/properties/SpawnerData.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/properties/SpawnerData.java @@ -131,6 +131,11 @@ public void recalculateAfterConfigReload() { recreateVirtualInventory(); } updateHologramData(); + + // Invalidate GUI cache after config reload + if (plugin.getSpawnerMenuUI() != null) { + plugin.getSpawnerMenuUI().invalidateSpawnerCache(this.spawnerId); + } } private void calculateStackBasedValues() { @@ -195,6 +200,11 @@ private void updateStackSize(int newStackSize) { this.lastSpawnTime = System.currentTimeMillis(); updateHologramData(); + + // Invalidate GUI cache when stack size changes + if (plugin.getSpawnerMenuUI() != null) { + plugin.getSpawnerMenuUI().invalidateSpawnerCache(this.spawnerId); + } } private void recreateVirtualInventory() { @@ -227,6 +237,11 @@ private void transferItemsToNewInventory(Map