Skip to content

Commit d070a21

Browse files
committed
feat: add spawner mob head info button and update GUI configurations
1 parent ed5786a commit d070a21

10 files changed

Lines changed: 192 additions & 23 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ private void handleControlSlotClick(Player player, int slot, StoragePageHolder h
162162
case "return_main":
163163
handleReturnToMainMenu(player, spawner);
164164
break;
165+
case "none":
166+
// Display-only button — consume click, do nothing
167+
break;
165168
default:
166169
// Unknown action, log warning
167170
plugin.getLogger().warning("Unknown storage action: " + action);

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,30 @@
22

33
import github.nighter.smartspawner.SmartSpawner;
44
import github.nighter.smartspawner.language.LanguageManager;
5+
import github.nighter.smartspawner.nms.VersionInitializer;
6+
import github.nighter.smartspawner.spawner.config.SpawnerMobHeadTexture;
57
import github.nighter.smartspawner.spawner.gui.layout.GuiButton;
68
import github.nighter.smartspawner.spawner.gui.layout.GuiLayout;
79
import github.nighter.smartspawner.spawner.gui.layout.GuiLayoutConfig;
10+
import github.nighter.smartspawner.spawner.lootgen.loot.EntityLootConfig;
11+
import github.nighter.smartspawner.spawner.lootgen.loot.LootItem;
812
import github.nighter.smartspawner.spawner.properties.VirtualInventory;
913
import github.nighter.smartspawner.spawner.properties.SpawnerData;
1014
import github.nighter.smartspawner.Scheduler;
1115
import github.nighter.smartspawner.Scheduler.Task;
1216
import lombok.Getter;
17+
import net.kyori.adventure.text.Component;
18+
import org.bukkit.entity.EntityType;
1319
import org.bukkit.inventory.Inventory;
20+
import org.bukkit.inventory.ItemFlag;
1421
import org.bukkit.inventory.ItemStack;
1522
import org.bukkit.inventory.meta.ItemMeta;
1623
import org.bukkit.Bukkit;
1724
import org.bukkit.Material;
1825

1926
import java.util.*;
2027
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.function.Consumer;
2129

2230
public class SpawnerStorageUI {
2331
private static final int INVENTORY_SIZE = 54;
@@ -324,6 +332,12 @@ private void addNavigationButtons(Map<Integer, ItemStack> updates, SpawnerData s
324332
continue;
325333
}
326334

335+
// Handle info_button (spawner mob head with loot info)
336+
if (button.isInfoButton()) {
337+
updates.put(button.getSlot(), createStorageSpawnerInfoButton(spawner, button.getMaterial()));
338+
continue;
339+
}
340+
327341
String action = getAnyActionFromButton(button);
328342
if (action == null) continue;
329343

@@ -521,6 +535,103 @@ private ItemStack createSortButton(SpawnerData spawner, Material material) {
521535
return createButton(material, name, lore);
522536
}
523537

538+
private ItemStack createStorageSpawnerInfoButton(SpawnerData spawner, Material material) {
539+
Map<VirtualInventory.ItemSignature, Long> storedItems = spawner.getVirtualInventory().getConsolidatedItems();
540+
List<Component> lootComponents = buildStorageInfoLootComponents(spawner, storedItems);
541+
542+
Map<String, String> placeholders = new HashMap<>();
543+
String entityName;
544+
if (spawner.isItemSpawner()) {
545+
entityName = languageManager.getVanillaItemName(spawner.getSpawnedItemMaterial());
546+
} else {
547+
entityName = languageManager.getFormattedMobName(spawner.getEntityType());
548+
}
549+
placeholders.put("entity", entityName);
550+
placeholders.put("\u1d07\u0274\u1d1b\u026a\u1d1b\u028f", languageManager.getSmallCaps(entityName));
551+
placeholders.put("stack_size", String.valueOf(spawner.getStackSize()));
552+
553+
int currentItems = spawner.getVirtualInventory().getUsedSlots();
554+
int maxSlots = spawner.getMaxSpawnerLootSlots();
555+
double percentStorageDecimal = maxSlots > 0 ? ((double) currentItems / maxSlots) * 100 : 0;
556+
placeholders.put("percent_storage_decimal", String.format("%.1f", percentStorageDecimal));
557+
placeholders.put("percent_storage_rounded", String.valueOf((int) Math.round(percentStorageDecimal)));
558+
559+
long currentExp = spawner.getSpawnerExp();
560+
long maxExp = spawner.getMaxStoredExp();
561+
double percentExpDecimal = maxExp > 0 ? ((double) currentExp / maxExp) * 100 : 0;
562+
placeholders.put("percent_exp_decimal", String.format("%.1f", percentExpDecimal));
563+
placeholders.put("percent_exp_rounded", String.valueOf((int) Math.round(percentExpDecimal)));
564+
565+
Consumer<ItemMeta> metaModifier = meta -> {
566+
meta.setDisplayName(languageManager.getGuiItemName("storage_spawner_info_button.name", placeholders));
567+
List<Component> lore = languageManager.buildGuiLoreAsComponents(
568+
"storage_spawner_info_button.lore", placeholders, lootComponents,
569+
"storage_spawner_info_button.loot_items_empty");
570+
if (!lore.isEmpty()) {
571+
meta.lore(lore);
572+
}
573+
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE);
574+
};
575+
576+
ItemStack item;
577+
if (spawner.isItemSpawner()) {
578+
item = SpawnerMobHeadTexture.getItemSpawnerHead(spawner.getSpawnedItemMaterial(), metaModifier);
579+
} else {
580+
item = SpawnerMobHeadTexture.getCustomHead(spawner.getEntityType(), metaModifier);
581+
}
582+
583+
if (item.getType() == Material.SPAWNER) {
584+
VersionInitializer.hideTooltip(item);
585+
}
586+
587+
return item;
588+
}
589+
590+
private List<Component> buildStorageInfoLootComponents(SpawnerData spawner,
591+
Map<VirtualInventory.ItemSignature, Long> storedItems) {
592+
Map<Material, Long> materialAmountMap = new HashMap<>();
593+
for (Map.Entry<VirtualInventory.ItemSignature, Long> entry : storedItems.entrySet()) {
594+
Material mat = entry.getKey().getTemplateRef().getType();
595+
materialAmountMap.merge(mat, entry.getValue(), Long::sum);
596+
}
597+
598+
EntityLootConfig lootConfig;
599+
if (spawner.isItemSpawner()) {
600+
lootConfig = plugin.getItemSpawnerSettingsConfig().getLootConfig(spawner.getSpawnedItemMaterial());
601+
} else {
602+
lootConfig = plugin.getSpawnerSettingsConfig().getLootConfig(spawner.getEntityType());
603+
}
604+
List<LootItem> possibleLootItems = lootConfig != null ? lootConfig.getAllItems() : Collections.emptyList();
605+
606+
if (possibleLootItems.isEmpty() && storedItems.isEmpty()) {
607+
return Collections.emptyList();
608+
}
609+
610+
List<Component> components = new ArrayList<>();
611+
if (!possibleLootItems.isEmpty()) {
612+
possibleLootItems.sort(Comparator.comparing(item -> item.material().name()));
613+
for (LootItem lootItem : possibleLootItems) {
614+
Material mat = lootItem.material();
615+
long amount = materialAmountMap.getOrDefault(mat, 0L);
616+
String formattedAmount = languageManager.formatNumber(amount);
617+
String chance = String.format("%.1f", lootItem.chance()) + "%";
618+
components.add(languageManager.buildTranslatableGuiLootLine(
619+
"storage_spawner_info_button.loot_items", mat, formattedAmount, chance));
620+
}
621+
} else {
622+
List<Map.Entry<VirtualInventory.ItemSignature, Long>> sortedItems = new ArrayList<>(storedItems.entrySet());
623+
sortedItems.sort(Comparator.comparing(e -> e.getKey().getMaterialName()));
624+
for (Map.Entry<VirtualInventory.ItemSignature, Long> entry : sortedItems) {
625+
Material mat = entry.getKey().getTemplateRef().getType();
626+
long amount = entry.getValue();
627+
String formattedAmount = languageManager.formatNumber(amount);
628+
components.add(languageManager.buildTranslatableGuiLootLine(
629+
"storage_spawner_info_button.loot_items", mat, formattedAmount, ""));
630+
}
631+
}
632+
return components;
633+
}
634+
524635
private void startCleanupTask() {
525636
cleanupTask = Scheduler.runTaskTimer(this::cleanupCaches, 20L * 30, 20L * 30); // Run every 30 seconds
526637
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
#
55
# Format:
66
# slot_X: # X = slot number (1-9)
7-
# material: MATERIAL_NAME # Material to display
7+
# material: MATERIAL_NAME # Material to display (use PLAYER_HEAD for spawner mob head)
88
# enabled: true/false # Show/hide button
9+
# info_button: true # Optional: Mark as spawner mob-head info button (shows loot list)
910
# click: "action" # Action for any click (left/right/shift)
1011
# if: # Optional: conditional actions
1112
# condition_name:
@@ -14,6 +15,20 @@
1415
# Conditions:
1516
# sell_integration: When sell/shop integration is enabled
1617
# no_sell_integration: When sell/shop integration is disabled
18+
#
19+
# Actions:
20+
# previous_page: Go to previous page
21+
# next_page: Go to next page
22+
# sort_items: Sort items in storage
23+
# open_filter: Open filter configuration
24+
# sell_all: Sell all items (no exp collection)
25+
# sell_and_exp: Sell all items and collect exp
26+
# collect_exp: Collect stored experience only
27+
# return_main: Return to main menu
28+
# take_all: Take all items from current page
29+
# drop_page: Drop all items from current page
30+
# none: Display only, no action (use with info_button: true for mob-head display)
31+
#
1732

1833
# Return to main menu
1934
slot_1:

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
#
55
# Format:
66
# slot_X: # X = slot number (1-9)
7-
# material: MATERIAL_NAME # Material to display
7+
# material: MATERIAL_NAME # Material to display (use PLAYER_HEAD for spawner mob head)
88
# enabled: true/false # Show/hide button
9+
# info_button: true # Optional: Mark as spawner mob-head info button (shows loot list)
910
# click: "action" # Action for any click (left/right/shift)
1011
# if: # Optional: conditional actions
1112
# condition_name:
@@ -14,21 +15,26 @@
1415
# Conditions:
1516
# sell_integration: When sell/shop integration is enabled
1617
# no_sell_integration: When sell/shop integration is disabled
18+
#
19+
# Actions:
20+
# previous_page: Go to previous page
21+
# next_page: Go to next page
22+
# sort_items: Sort items in storage
23+
# open_filter: Open filter configuration
24+
# sell_all: Sell all items (no exp collection)
25+
# sell_and_exp: Sell all items and collect exp
26+
# collect_exp: Collect stored experience only
27+
# return_main: Return to main menu
28+
# take_all: Take all items from current page
29+
# drop_page: Drop all items from current page
30+
# none: Display only, no action (use with info_button: true for mob-head display)
1731

18-
# Return to main menu
1932
slot_1:
20-
material: BARRIER
21-
enabled: true
22-
click: "return_main"
23-
24-
# Previous page
25-
slot_4:
2633
material: ARROW
2734
enabled: true
2835
click: "previous_page"
2936

30-
# Multi-purpose button
31-
slot_5:
37+
slot_4:
3238
material: GOLD_INGOT
3339
enabled: true
3440
if:
@@ -38,14 +44,18 @@ slot_5:
3844
material: SPECTRAL_ARROW
3945
click: "take_all"
4046

41-
# Next page
42-
slot_6:
43-
material: ARROW
47+
slot_5:
48+
material: PLAYER_HEAD
4449
enabled: true
45-
click: "next_page"
50+
info_button: true
51+
click: "none"
4652

47-
# Drop page
48-
slot_9:
53+
slot_6:
4954
material: DROPPER
5055
enabled: true
5156
click: "drop_page"
57+
58+
slot_9:
59+
material: ARROW
60+
enabled: true
61+
click: "next_page"

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
#
55
# Format:
66
# slot_X: # X = slot number (1-9)
7-
# material: MATERIAL_NAME # Material to display
7+
# material: MATERIAL_NAME # Material to display (use PLAYER_HEAD for spawner mob head)
88
# enabled: true/false # Show/hide button
9+
# info_button: true # Optional: Mark as spawner mob-head info button (shows loot list)
910
# click: "action" # Action for any click (left/right/shift)
1011
# left_click: "action" # Action for left click only (overrides click)
1112
# right_click: "action" # Action for right click only (overrides click)
@@ -28,6 +29,7 @@
2829
# return_main: Return to main menu
2930
# take_all: Take all items from current page
3031
# drop_page: Drop all items from current page
32+
# none: Display only, no action (use with info_button: true for mob-head display)
3133

3234

3335
# Previous page button

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ collect_exp_button:
203203
lore:
204204
- '&#2ecc71⊳ &fClick to claim your XP'
205205

206+
storage_spawner_info_button:
207+
name: ''
208+
lore:
209+
- ''
210+
206211
# ──────────────────────────────────────────────────────
207212
# Filter Configuration GUI
208213
# ──────────────────────────────────────────────────────

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ button_sell_info:
145145
# {entity} - Formatted entity name (e.g., "Zombie")
146146
# {ᴇɴᴛɪᴛʏ} - Entity name in small caps (e.g., "ᴢᴏᴍʙɪᴇ")
147147
# {amount} - Number of spawners stacked
148-
gui_title_storage: '&#545454{amount} {ᴇɴᴛɪᴛʏ} ꜱᴘᴀᴡɴᴇʀꜱ'
148+
gui_title_storage: '&#545454{amount} {ᴇɴᴛɪᴛʏ} ꜱᴘᴀᴡɴᴇʀ'
149149

150150
navigation_button_previous:
151151
name: '&#7AFC00ᴘʀᴇᴠɪᴏᴜꜱ'
@@ -158,14 +158,14 @@ navigation_button_next:
158158
- '&fClick to go forward a page'
159159

160160
sell_button:
161-
name: '&#00F986ꜱᴇʟʟ ᴀʟʟ'
161+
name: '&#FC0000ꜱᴇʟʟ ᴀʟʟ'
162162
lore:
163-
- '&fClick to sell all mob drops'
163+
- '&fClick to sell all mob drops!'
164164

165165
sell_and_exp_button:
166-
name: '&aꜱᴇʟʟ & ᴇxᴘ'
166+
name: '&#FC0000ꜱᴇʟʟ & ᴇxᴘ'
167167
lore:
168-
- '&fClick to sell items and collect experience'
168+
- '&fClick to sell items and collect experience!'
169169

170170
return_button:
171171
name: '&#FC0000ʀᴇᴛᴜʀɴ'
@@ -203,6 +203,15 @@ collect_exp_button:
203203
lore:
204204
- '&#2ecc71⊳ &fClick to claim your XP'
205205

206+
storage_spawner_info_button:
207+
name: '&#00F986{stack_size} {ᴇɴᴛɪᴛʏ} ꜱᴘᴀᴡɴᴇʀ'
208+
lore:
209+
- '{loot_items}'
210+
- '&#00F986({percent_storage_decimal}% filled)'
211+
- '&#7AFC00({percent_exp_decimal}% xp stored)'
212+
loot_items: '&#00F986{amount} &f{item_name}'
213+
loot_items_empty: '&#e6e6faɴᴏ ɪᴛᴇᴍ ᴅʀᴏᴘs'
214+
206215
# ──────────────────────────────────────────────────────
207216
# Filter Configuration GUI
208217
# ──────────────────────────────────────────────────────

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ collect_exp_button:
276276
lore:
277277
- '&#2ecc71⊳ &#f8f8ffᴋʟɪᴄᴋᴇɴ ᴜᴍ ᴇxᴘ ᴀʙᴢᴜʜᴏʟᴇɴ'
278278

279+
storage_spawner_info_button:
280+
name: ''
281+
lore:
282+
- ''
283+
279284
# ──────────────────────────────────────────────────────
280285
# Filter Configuration GUI
281286
# ──────────────────────────────────────────────────────

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ collect_exp_button:
265265
lore:
266266
- '&#2ecc71⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴄᴏʟʟᴇᴄᴛ xᴘ'
267267

268+
storage_spawner_info_button:
269+
name: ''
270+
lore:
271+
- ''
272+
268273
# ──────────────────────────────────────────────────────
269274
# Filter Configuration GUI
270275
# ──────────────────────────────────────────────────────

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ collect_exp_button:
266266
lore:
267267
- '&#2ecc71⊳ &#f8f8ffɴʜấᴘ để ɴʜậɴ ᴇxᴘ'
268268

269+
storage_spawner_info_button:
270+
name: ''
271+
lore:
272+
- ''
269273

270274
# ──────────────────────────────────────────────────────
271275
# Filter Configuration GUI

0 commit comments

Comments
 (0)