From 45bb27299e716fc4e588c320348440249795d92b Mon Sep 17 00:00:00 2001
From: alexteens24
Date: Wed, 29 Jul 2026 07:41:23 +0700
Subject: [PATCH] fix GUI layout reload and empty sell navigation
---
.../spawner/gui/main/SpawnerMenuAction.java | 7 +++-
.../gui/storage/SpawnerStorageAction.java | 6 ++--
.../updates/GuiLayoutUpdater.java | 3 ++
.../smartspawner/updates/YamlMigrator.java | 33 ++++++++++++++-----
4 files changed, 37 insertions(+), 12 deletions(-)
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 b19227f2..e4ba56b6 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
@@ -172,7 +172,12 @@ private boolean handleLayoutAction(GuiLayout layout, Player player, SpawnerData
}
// If no items to sell, still allow exp collection
if (spawner.getVirtualInventory().getUsedSlots() == 0) {
- boolean success = handleExpBottleAcceptedClick(player, spawner, true);
+ boolean success = false;
+ if (spawner.getSpawnerExp() > 0) {
+ success = tryCollectExpForPlayer(player, spawner);
+ } else {
+ messageService.sendMessage(player, "spawner_storage_empty");
+ }
playActionResult(player, button, clickType, success);
return true;
}
diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageAction.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageAction.java
index 64567717..6dd57021 100644
--- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageAction.java
+++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageAction.java
@@ -227,10 +227,10 @@ private void handleSellAction(Player player, SpawnerData spawner, boolean collec
// Check if there are items to sell
if (spawner.getVirtualInventory().getUsedSlots() == 0) {
- if (collectExp) {
- // No items to sell but still collect exp
+ if (collectExp && spawner.getSpawnerExp() > 0) {
+ // No items to sell, but collect available exp without leaving the storage GUI
boolean success = plugin.getSpawnerMenuAction()
- .handleExpBottleAcceptedClick(player, spawner, true);
+ .tryCollectExpForPlayer(player, spawner);
playActionResult(player, sourceButton, sourceClickType, success);
} else {
messageService.sendMessage(player, "spawner_storage_empty");
diff --git a/core/src/main/java/github/nighter/smartspawner/updates/GuiLayoutUpdater.java b/core/src/main/java/github/nighter/smartspawner/updates/GuiLayoutUpdater.java
index 12f33de4..18beb931 100644
--- a/core/src/main/java/github/nighter/smartspawner/updates/GuiLayoutUpdater.java
+++ b/core/src/main/java/github/nighter/smartspawner/updates/GuiLayoutUpdater.java
@@ -39,6 +39,9 @@ public void checkAndUpdateLayouts() {
plugin.getResource(resource),
java.util.List.of(),
ConfigMigrations.GUI_LAYOUT,
+ true,
+ YamlMigrator.OwnedSection.fullyUserManaged(
+ (defaults, path) -> !path.contains(".") && path.startsWith("slot_")),
plugin.getLogger());
}
}
diff --git a/core/src/main/java/github/nighter/smartspawner/updates/YamlMigrator.java b/core/src/main/java/github/nighter/smartspawner/updates/YamlMigrator.java
index b69360cd..f8b9cbdb 100644
--- a/core/src/main/java/github/nighter/smartspawner/updates/YamlMigrator.java
+++ b/core/src/main/java/github/nighter/smartspawner/updates/YamlMigrator.java
@@ -71,18 +71,22 @@ public interface SectionMatcher {
* children back would resurrect deleted drops, or put a chat line back under a message the
* owner turned into an action bar.
*
- * Either way, once the user has the section its contents are left alone. The two factories
+ *
Either way, once the user has the section its contents are left alone. The factories
* differ only in what an absent section means, which is not the same question for a
- * drop list as for a message.
+ * drop list, a message, or a fully user-managed GUI button list.
*/
- public record OwnedSection(SectionMatcher matcher, boolean keepDeleted) {
+ public record OwnedSection(SectionMatcher matcher, boolean keepDeleted, boolean alwaysLocked) {
+
+ public OwnedSection(SectionMatcher matcher, boolean keepDeleted) {
+ this(matcher, keepDeleted, false);
+ }
/**
* For a list the user curates. A section they removed entirely stays removed, as long as
* they still have its parent. Deleting a mob's whole {@code loot} block has to stick.
*/
public static OwnedSection curated(SectionMatcher matcher) {
- return new OwnedSection(matcher, true);
+ return new OwnedSection(matcher, true, false);
}
/**
@@ -92,7 +96,15 @@ public static OwnedSection curated(SectionMatcher matcher) {
* theirs; only a wholly absent message is refilled.
*/
public static OwnedSection restoredWhenAbsent(SectionMatcher matcher) {
- return new OwnedSection(matcher, false);
+ return new OwnedSection(matcher, false, false);
+ }
+
+ /**
+ * For sections whose presence and contents are both controlled by the user. Matching
+ * default sections are never topped up, even when the user removed or relocated them.
+ */
+ public static OwnedSection fullyUserManaged(SectionMatcher matcher) {
+ return new OwnedSection(matcher, true, true);
}
}
@@ -247,14 +259,19 @@ private static int addMissingKeys(YamlConfiguration user, YamlConfiguration defa
* leaf of a brand new section create that section, after which every remaining leaf of it looks
* user-owned and gets skipped, leaving the section half filled.
*
- * A section the user has is always owned. A section they do not have is owned only for a
- * {@link OwnedSection#curated} marker, and then only when they still have its parent, which is
- * what tells a block they deleted apart from one they have never seen.
+ * A section the user has is always owned. A fully user-managed section is also owned when
+ * absent. Other absent sections are owned only for a {@link OwnedSection#curated} marker, and
+ * then only when they still have its parent, which tells a block they deleted apart from one
+ * they have never seen.
*/
private static Set lockedSections(YamlConfiguration user, YamlConfiguration defaults, OwnedSection owned) {
Set locked = new HashSet<>();
for (String path : defaults.getKeys(true)) {
if (!defaults.isConfigurationSection(path) || !owned.matcher().matches(defaults, path)) continue;
+ if (owned.alwaysLocked()) {
+ locked.add(path);
+ continue;
+ }
if (user.isConfigurationSection(path)) {
locked.add(path);
continue;