Skip to content

Commit bc3f5cb

Browse files
authored
Merge pull request #23 from NighterDevelopment/copilot/fix-ec45c5c9-739b-40d1-a098-ee3400726b64
Implement items layout sorter for spawner storage GUI and update vi_VN translations
2 parents 04d00b7 + ea7f41d commit bc3f5cb

10 files changed

Lines changed: 245 additions & 27 deletions

File tree

core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageAction.java

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import java.util.*;
4141
import java.util.concurrent.ConcurrentHashMap;
42+
import java.util.stream.Collectors;
4243

4344
public class SpawnerStorageAction implements Listener {
4445
private final SmartSpawner plugin;
@@ -162,8 +163,8 @@ private void handleControlSlotClick(Player player, int slot, StoragePageHolder h
162163
}
163164

164165
switch (action) {
165-
case "discard_all":
166-
handleDiscardAllItems(player, spawner, inventory);
166+
case "sort_items":
167+
handleSortItemsClick(player, spawner, inventory);
167168
break;
168169
case "item_filter":
169170
openFilterConfig(player, spawner);
@@ -217,8 +218,8 @@ private void handleLegacyButtonType(Player player, int slot, StoragePageHolder h
217218
String buttonType = buttonTypeOpt.get();
218219

219220
switch (buttonType) {
220-
case "discard_all":
221-
handleDiscardAllItems(player, spawner, inventory);
221+
case "sort_items":
222+
handleSortItemsClick(player, spawner, inventory);
222223
break;
223224
case "item_filter":
224225
openFilterConfig(player, spawner);
@@ -587,6 +588,84 @@ private void handleDiscardAllItems(Player player, SpawnerData spawner, Inventory
587588
}
588589
}
589590

591+
private void handleSortItemsClick(Player player, SpawnerData spawner, Inventory inventory) {
592+
// Use same permission as storage access
593+
if (!player.hasPermission("smartspawner.storage")) {
594+
messageService.sendMessage(player, "no_permission");
595+
return;
596+
}
597+
598+
// Get available loot items
599+
if (spawner.getLootConfig() == null || spawner.getLootConfig().getLootItems() == null) {
600+
return; // No items to sort by
601+
}
602+
603+
var lootItems = spawner.getLootConfig().getLootItems();
604+
if (lootItems.isEmpty()) {
605+
return; // No items to sort by
606+
}
607+
608+
// Get current sort item
609+
Material currentSort = spawner.getPreferredSortItem();
610+
611+
// Find next sort item in the list
612+
Material nextSort = null;
613+
boolean foundCurrent = false;
614+
615+
// Sort items for consistent ordering
616+
var sortedLoot = lootItems.stream()
617+
.map(item -> item.getMaterial())
618+
.sorted(Comparator.comparing(Material::name))
619+
.collect(Collectors.toList());
620+
621+
if (currentSort == null) {
622+
// No current sort, select first item
623+
nextSort = sortedLoot.get(0);
624+
} else {
625+
// Find current item and select next one
626+
for (int i = 0; i < sortedLoot.size(); i++) {
627+
if (sortedLoot.get(i) == currentSort) {
628+
// Found current, get next (or wrap to first)
629+
nextSort = sortedLoot.get((i + 1) % sortedLoot.size());
630+
foundCurrent = true;
631+
break;
632+
}
633+
}
634+
635+
// If current sort item is not in the loot list anymore, reset to first
636+
if (!foundCurrent) {
637+
nextSort = sortedLoot.get(0);
638+
}
639+
}
640+
641+
// Set new sort preference
642+
spawner.setPreferredSortItem(nextSort);
643+
644+
// Mark spawner as modified to save the preference
645+
spawnerManager.queueSpawnerForSaving(spawner.getSpawnerId());
646+
647+
// Re-sort the virtual inventory
648+
spawner.getVirtualInventory().sortItems(nextSort);
649+
650+
// Update the display
651+
StoragePageHolder holder = (StoragePageHolder) inventory.getHolder(false);
652+
if (holder != null) {
653+
updatePageContent(player, spawner, holder.getCurrentPage(), inventory, false);
654+
}
655+
656+
// Play sound and show feedback
657+
player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1.0f, 1.2f);
658+
659+
String itemName = languageManager.getVanillaItemName(nextSort);
660+
Map<String, String> placeholders = new HashMap<>();
661+
placeholders.put("item", itemName);
662+
messageService.sendMessage(player, "sort_changed", placeholders);
663+
664+
if (!spawner.isInteracted()) {
665+
spawner.markInteracted();
666+
}
667+
}
668+
590669
private void openLootPage(Player player, SpawnerData spawner, int page, boolean refresh) {
591670
SpawnerStorageUI lootManager = plugin.getSpawnerStorageUI();
592671
String title = languageManager.getGuiTitle("gui_title_storage");

core/src/main/java/github/nighter/smartspawner/spawner/gui/storage/SpawnerStorageUI.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.*;
2020
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.stream.Collectors;
2122

2223
public class SpawnerStorageUI {
2324
private static final int INVENTORY_SIZE = 54;
@@ -90,13 +91,13 @@ private void initializeStaticButtons() {
9091
));
9192
}
9293

93-
// Create discard all button
94-
GuiButton discardAllButton = layout.getButton("discard_all");
95-
if (discardAllButton != null) {
96-
staticButtons.put("discardAll", createButton(
97-
discardAllButton.getMaterial(),
98-
languageManager.getGuiItemName("discard_all_button.name"),
99-
languageManager.getGuiItemLoreAsList("discard_all_button.lore")
94+
// Create sort items button
95+
GuiButton sortItemsButton = layout.getButton("sort_items");
96+
if (sortItemsButton != null) {
97+
staticButtons.put("sortItems", createButton(
98+
sortItemsButton.getMaterial(),
99+
languageManager.getGuiItemName("sort_items_button.name"),
100+
languageManager.getGuiItemLoreAsList("sort_items_button.lore")
100101
));
101102
}
102103

@@ -264,10 +265,11 @@ private void addNavigationButtons(Map<Integer, ItemStack> updates, SpawnerData s
264265
updates.put(takeAllButton.getSlot(), staticButtons.get("takeAll"));
265266
}
266267

267-
// Add discard all button if enabled
268-
if (layout.hasButton("discard_all")) {
269-
GuiButton discardAllButton = layout.getButton("discard_all");
270-
updates.put(discardAllButton.getSlot(), staticButtons.get("discardAll"));
268+
// Add sort items button if enabled
269+
if (layout.hasButton("sort_items")) {
270+
GuiButton sortItemsButton = layout.getButton("sort_items");
271+
ItemStack sortButton = createSortButton(spawner, sortItemsButton.getMaterial());
272+
updates.put(sortItemsButton.getSlot(), sortButton);
271273
}
272274

273275
// Add drop page button if enabled
@@ -339,6 +341,43 @@ private ItemStack createSellButton(Material material) {
339341
return createButton(material, name, lore);
340342
}
341343

344+
private ItemStack createSortButton(SpawnerData spawner, Material material) {
345+
Map<String, String> placeholders = new HashMap<>();
346+
347+
// Get current sort item
348+
Material currentSort = spawner.getPreferredSortItem();
349+
String currentSortName = currentSort != null ?
350+
languageManager.getVanillaItemName(currentSort) : "&#f8f8ffNone";
351+
placeholders.put("current_sort_item", " &#37eb9a• " + currentSortName);
352+
353+
// Get available items from spawner drops
354+
StringBuilder availableItems = new StringBuilder();
355+
if (spawner.getLootConfig() != null && spawner.getLootConfig().getLootItems() != null) {
356+
boolean first = true;
357+
var sortedLoot = spawner.getLootConfig().getLootItems().stream()
358+
.sorted(Comparator.comparing(item -> item.getMaterial().name()))
359+
.collect(java.util.stream.Collectors.toList());
360+
361+
for (var lootItem : sortedLoot) {
362+
if (!first) availableItems.append("\n");
363+
String itemName = languageManager.getVanillaItemName(lootItem.getMaterial());
364+
String color = currentSort == lootItem.getMaterial() ? "&#37eb9a" : "&#bdc3c7";
365+
availableItems.append(" ").append(color).append("• ").append(itemName);
366+
first = false;
367+
}
368+
}
369+
370+
if (availableItems.length() == 0) {
371+
availableItems.append(" &#bdc3c7• None");
372+
}
373+
374+
placeholders.put("available_items", availableItems.toString());
375+
376+
String name = languageManager.getGuiItemName("sort_items_button.name", placeholders);
377+
List<String> lore = languageManager.getItemLoreWithMultilinePlaceholders("sort_items_button.lore", placeholders);
378+
return createButton(material, name, lore);
379+
}
380+
342381
private void startCleanupTask() {
343382
cleanupTask = Scheduler.runTaskTimer(this::cleanupCaches, 20L * 30, 20L * 30); // Run every 30 seconds
344383
}

core/src/main/java/github/nighter/smartspawner/spawner/properties/SpawnerData.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public class SpawnerData {
9292
@Getter @Setter
9393
private long cachedSpawnDelay = 0;
9494

95+
// Sort preference for spawner storage
96+
@Getter @Setter
97+
private Material preferredSortItem;
98+
9599
public SpawnerData(String id, Location location, EntityType type, SmartSpawner plugin) {
96100
this.plugin = plugin;
97101
this.spawnerId = id;
@@ -112,6 +116,7 @@ private void initializeDefaults() {
112116
this.isAtCapacity = false;
113117
this.stackSize = 1;
114118
this.lastSpawnTime = System.currentTimeMillis();
119+
this.preferredSortItem = null; // Initialize sort preference as null
115120
}
116121

117122
public void loadConfigurationValues() {

core/src/main/java/github/nighter/smartspawner/spawner/properties/VirtualInventory.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.util.*;
88
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.stream.Collectors;
910

1011
public class VirtualInventory {
1112
private final Map<ItemSignature, Long> consolidatedItems;
@@ -302,4 +303,45 @@ private void updateMetricsCache() {
302303
public boolean isDirty() {
303304
return displayCacheDirty;
304305
}
306+
307+
/**
308+
* Sorts items with the specified material type prioritized first.
309+
* This method optimizes by only invalidating caches when necessary.
310+
*
311+
* @param preferredMaterial The material to sort first, or null for no preference
312+
*/
313+
public void sortItems(org.bukkit.Material preferredMaterial) {
314+
// Clear the sorted cache to force re-sorting with new preference
315+
this.sortedEntriesCache = null;
316+
317+
// Only proceed if we have items to sort
318+
if (consolidatedItems.isEmpty()) {
319+
this.displayCacheDirty = true;
320+
return;
321+
}
322+
323+
// Generate new sorted entries with preference
324+
if (preferredMaterial != null) {
325+
this.sortedEntriesCache = consolidatedItems.entrySet().stream()
326+
.sorted((e1, e2) -> {
327+
boolean e1Preferred = e1.getKey().getTemplate().getType() == preferredMaterial;
328+
boolean e2Preferred = e2.getKey().getTemplate().getType() == preferredMaterial;
329+
330+
if (e1Preferred && !e2Preferred) return -1;
331+
if (!e1Preferred && e2Preferred) return 1;
332+
333+
// Both preferred or both not preferred, sort by material name
334+
return e1.getKey().getMaterialName().compareTo(e2.getKey().getMaterialName());
335+
})
336+
.collect(java.util.stream.Collectors.toList());
337+
} else {
338+
// No preference, sort alphabetically by material name
339+
this.sortedEntriesCache = consolidatedItems.entrySet().stream()
340+
.sorted(Comparator.comparing(e -> e.getKey().getMaterialName()))
341+
.collect(java.util.stream.Collectors.toList());
342+
}
343+
344+
// Mark display cache as dirty to force regeneration
345+
this.displayCacheDirty = true;
346+
}
305347
}

core/src/main/java/github/nighter/smartspawner/spawner/utils/SpawnerFileHandler.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ private boolean saveSpawnerBatch(Map<String, SpawnerData> spawners) {
194194
// Save last interacted player separately
195195
spawnerData.set(path + ".lastInteractedPlayer", spawner.getLastInteractedPlayer());
196196

197+
// Save preferred sort item
198+
spawnerData.set(path + ".preferredSortItem", spawner.getPreferredSortItem() != null ?
199+
spawner.getPreferredSortItem().name() : null);
200+
197201
Set<Material> filteredItems = spawner.getFilteredItems();
198202
if (filteredItems != null && !filteredItems.isEmpty()) {
199203
List<String> materials = filteredItems.stream()
@@ -440,6 +444,17 @@ private SpawnerData loadSpawnerFromConfig(String spawnerId, boolean logErrors) {
440444
// Load last interacted player
441445
String lastInteractedPlayer = spawnerData.getString(path + ".lastInteractedPlayer");
442446
spawner.setLastInteractedPlayer(lastInteractedPlayer);
447+
448+
// Load preferred sort item
449+
String preferredSortItemStr = spawnerData.getString(path + ".preferredSortItem");
450+
if (preferredSortItemStr != null && !preferredSortItemStr.isEmpty()) {
451+
try {
452+
Material preferredSortItem = Material.valueOf(preferredSortItemStr);
453+
spawner.setPreferredSortItem(preferredSortItem);
454+
} catch (IllegalArgumentException e) {
455+
logger.warning("Invalid preferred sort item for spawner " + spawnerId + ": " + preferredSortItemStr);
456+
}
457+
}
443458

444459
return spawner;
445460
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ buttons:
1010
material: SPECTRAL_ARROW
1111
enabled: true
1212

13-
# Discard all items button
14-
discard_all:
13+
# Item sort button (replaces discard all)
14+
sort_items:
1515
slot: 3
16-
material: CAULDRON
16+
material: COMPARATOR
1717
enabled: true
1818

1919
# Item filter configuration button

core/src/main/resources/language/en_US/gui.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,16 @@ item_filter_button:
198198
lore:
199199
- '&#3498db⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴄᴏɴꜰɪɢᴜʀᴇ'
200200

201-
discard_all_button:
202-
name: '&#ff5252ᴅɪꜱᴄᴀʀᴅ ᴀʟʟ ɪᴛᴇᴍꜱ'
201+
sort_items_button:
202+
name: '&#ffd700ꜱᴏʀᴛ ɪᴛᴇᴍꜱ'
203203
lore:
204-
- '&#e63939⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ &#ff5252ʀᴇᴍᴏᴠᴇ&#f8f8ff ᴀʟʟ ɪᴛᴇᴍꜱ'
204+
- '&#ffd700◈ ᴄᴜʀʀᴇɴᴛ ꜱᴏʀᴛ:'
205+
- ' %current_sort_item%'
206+
- ''
207+
- '&#ffd700◈ ᴀᴠᴀɪʟᴀʙʟᴇ ɪᴛᴇᴍꜱ:'
208+
- '%available_items%'
209+
- ''
210+
- '&#ffd700⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴄʏᴄʟᴇ ꜱᴏʀᴛ ᴏᴘᴛɪᴏɴ'
205211

206212
drop_page_button:
207213
name: '&#607D8Bᴅʀᴏᴘ ᴀʟʟ ɪᴛᴇᴍꜱ'

core/src/main/resources/language/en_US/messages.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,11 @@ spawner_management:
280280

281281
stack_decreased:
282282
message: "&#e6e6faꜱᴛᴀᴄᴋ &#ff6b6bᴅᴇᴄʀᴇᴀꜱᴇᴅ &#e6e6faby &#fce96a%amount% &#e6e6fa- ɴᴇᴡ ꜱɪᴢᴇ: &#37eb9a%new_size%"
283-
sound: entity.experience_orb.pickup
283+
sound: entity.experience_orb.pickup
284+
285+
# ------------------------------------------------------
286+
# Spawner Storage - Sort Messages
287+
# ------------------------------------------------------
288+
sort_changed:
289+
message: "&#ffd700ɪᴛᴇᴍ ꜱᴏʀᴛ ᴄʜᴀɴɢᴇᴅ ᴛᴏ: &#f8f8ff%item%"
290+
sound: entity.experience_orb.pickup

core/src/main/resources/language/vi_VN/gui.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,16 @@ item_filter_button:
145145
lore:
146146
- '&#7b68ee⊳ &#f8f8ffɴʜấᴘ ᴠàᴏ để ᴄàɪ đặᴛ'
147147

148-
discard_all_button:
149-
name: '&#ff5252ʟᴏạɪ ʙỏ ᴛấᴛ ᴄả ᴠậᴛ ᴘʜẩᴍ'
148+
sort_items_button:
149+
name: '&#ffd700sắᴘ xếᴘ ᴠậᴛ ᴘʜẩᴍ'
150150
lore:
151-
- '&#e63939⊳ &#f8f8ffɴʜấᴘ để &#ff5252xóᴀ&#f8f8ff ᴛấᴛ ᴄả ᴠậᴛ ᴘʜẩᴍ'
151+
- '&#ffd700◈ ᴄáᴄʜ sắᴘ xếᴘ ʜɪệɴ ᴛạɪ:'
152+
- ' %current_sort_item%'
153+
- ''
154+
- '&#ffd700◈ ᴠậᴛ ᴘʜẩᴍ ᴄó sẵɴ:'
155+
- '%available_items%'
156+
- ''
157+
- '&#ffd700⊳ &#f8f8ffɴʜấᴘ để ᴄʜuyểɴ đổɪ ᴄáᴄʜ sắᴘ xếᴘ'
152158

153159
drop_page_button:
154160
name: '&#7b68eeᴛʜả đồ'
@@ -357,4 +363,17 @@ price_item:
357363
- ''
358364
- '&#d9b50c◈ &#fce96aɢɪá ᴄʜɪ ᴛɪếᴛ:'
359365
- ' &#f8f8ff• ᴛùʏ ᴄʜỉɴʜ: &#37eb9a$%custom_price%'
360-
- ' &#f8f8ff• ᴄửᴀ ʜàɴɢ: &#37eb9a$%shop_price%'
366+
- ' &#f8f8ff• ᴄửᴀ ʜàɴɢ: &#37eb9a$%shop_price%'
367+
368+
# ---------------------------------------------------
369+
# Bedrock GUI
370+
# ---------------------------------------------------
371+
bedrock:
372+
main_gui:
373+
title_single_spawner: 'ʟồɴɢ sᴘᴀᴡɴ %entity%'
374+
title_stacked_spawner: '%amount% ʟồɴɢ sᴘᴀᴡɴ %entity%'
375+
376+
button_names:
377+
storage: Mở ᴋʜᴏ ᴄʜứᴀ
378+
exp: Thu ᴛʜậᴘ XP
379+
stacker: Qᴜảɴ ʟý xếᴘ ᴄʜồɴɢ

0 commit comments

Comments
 (0)