diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmListener.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmListener.java index ca19268d..b3757ce8 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmListener.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmListener.java @@ -70,6 +70,9 @@ private void handleCancel(Player player, SpawnerData spawner, SpawnerSellConfirm // Play sound instead of sending message player.playSound(player.getLocation(), org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 1.0f); + // Clear interaction state + spawner.clearInteracted(); + // Reopen previous GUI reopenPreviousGui(player, spawner, previousGui); } @@ -80,6 +83,9 @@ private void handleConfirm(Player player, SpawnerData spawner, SpawnerSellConfir plugin.getSpawnerMenuAction().handleExpBottleClick(player, spawner, true); } + // Clear interaction state + spawner.clearInteracted(); + // Trigger the actual sell operation plugin.getSpawnerSellManager().sellAllItems(player, spawner); diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmUI.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmUI.java index a3879c62..a3a741a8 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmUI.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/sell/SpawnerSellConfirmUI.java @@ -42,6 +42,15 @@ public void openSellConfirmGui(Player player, SpawnerData spawner, PreviousGui p return; } + // Check if there are items to sell before opening + if (spawner.getVirtualInventory().getUsedSlots() == 0) { + plugin.getMessageService().sendMessage(player, "no_items"); + return; + } + + // Mark spawner as interacted to lock state during transaction + spawner.markInteracted(); + // Cache title - no placeholders needed String title = languageManager.getGuiTitle("gui_title_sell_confirm", null); Inventory gui = Bukkit.createInventory(new SpawnerSellConfirmHolder(spawner, previousGui, collectExp), GUI_SIZE, title); diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerHandler.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerHandler.java index d104971a..986d75bf 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerHandler.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerHandler.java @@ -55,6 +55,8 @@ public class SpawnerStackerHandler implements Listener { private static final int[] INCREASE_SLOTS = {17, 16, 15}; private static final int SPAWNER_INFO_SLOT = 13; private static final int[] STACK_AMOUNTS = {64, 10, 1}; + private static final int REMOVE_ALL_SLOT = 22; + private static final int ADD_ALL_SLOT = 4; // Player interaction tracking - using more efficient data structures private final Map lastClickTime = new ConcurrentHashMap<>(16, 0.75f, 2); @@ -135,6 +137,29 @@ public void onInventoryClick(InventoryClickEvent event) { // Process stack modification int slotIndex = event.getRawSlot(); + + // Handle "all" buttons + if (slotIndex == ADD_ALL_SLOT) { + lastClickTime.put(playerId, System.currentTimeMillis()); + handleAddAll(player, spawner); + String spawnerId = spawner.getSpawnerId(); + Set viewers = activeViewers.get(spawnerId); + if (viewers != null && !viewers.isEmpty()) { + scheduleViewersUpdate(spawner); + } + return; + } + if (slotIndex == REMOVE_ALL_SLOT) { + lastClickTime.put(playerId, System.currentTimeMillis()); + handleRemoveAll(player, spawner); + String spawnerId = spawner.getSpawnerId(); + Set viewers = activeViewers.get(spawnerId); + if (viewers != null && !viewers.isEmpty()) { + scheduleViewersUpdate(spawner); + } + return; + } + int changeAmount = determineChangeAmount(slotIndex); if (changeAmount != 0) { @@ -157,7 +182,7 @@ public void onInventoryDrag(InventoryDragEvent event) { if (!(event.getInventory().getHolder(false) instanceof SpawnerStackerHolder holder)) { return; } - + event.setCancelled(true); } @@ -419,6 +444,167 @@ private void handleStackIncrease(Player player, SpawnerData spawner, int changeA player.playSound(player.getLocation(), STACK_SOUND, SOUND_VOLUME, SOUND_PITCH); } + private void handleAddAll(Player player, SpawnerData spawner) { + int currentSize = spawner.getStackSize(); + int maxStackSize = spawner.getMaxStackSize(); + int spaceLeft = maxStackSize - currentSize; + + if (spaceLeft <= 0) { + Map placeholders = new HashMap<>(2); + placeholders.put("max", String.valueOf(maxStackSize)); + messageService.sendMessage(player, "spawner_stack_full", placeholders); + return; + } + + // Scan inventory for matching spawners + InventoryScanResult scanResult; + if (spawner.isItemSpawner()) { + scanResult = scanPlayerInventoryForItemSpawner(player, spawner.getSpawnedItemMaterial()); + } else { + scanResult = scanPlayerInventory(player, spawner.getEntityType()); + } + + if (scanResult.availableSpawners == 0 && scanResult.hasDifferentType) { + messageService.sendMessage(player, "spawner_different"); + return; + } + + if (scanResult.availableSpawners == 0) { + Map placeholders = new HashMap<>(4); + placeholders.put("amountChange", String.valueOf(spaceLeft)); + placeholders.put("amountAvailable", "0"); + messageService.sendMessage(player, "spawner_insufficient_quantity", placeholders); + return; + } + + int actualChange = Math.min(spaceLeft, scanResult.availableSpawners); + + if (SpawnerStackEvent.getHandlerList().getRegisteredListeners().length != 0) { + SpawnerStackEvent e = new SpawnerStackEvent(player, spawner.getSpawnerLocation(), spawner.getStackSize(), + spawner.getStackSize() + actualChange, SpawnerStackEvent.StackSource.GUI); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) + return; + } + + // Update stack size first and ensure data is marked as modified + spawner.setStackSize(currentSize + actualChange); + spawnerManager.markSpawnerModified(spawner.getSpawnerId()); + + if (spawner.isInteracted()) { + player.playSound(player.getLocation(), STACK_SOUND, SOUND_VOLUME, SOUND_PITCH); + return; + } + + if (spawner.isItemSpawner()) { + removeValidItemSpawnersFromInventory(player, spawner.getSpawnedItemMaterial(), actualChange, + scanResult.spawnerSlots); + } else { + removeValidSpawnersFromInventory(player, spawner.getEntityType(), actualChange, + scanResult.spawnerSlots); + } + + player.playSound(player.getLocation(), STACK_SOUND, SOUND_VOLUME, SOUND_PITCH); + } + + private void handleRemoveAll(Player player, SpawnerData spawner) { + Location location = spawner.getSpawnerLocation(); + + if (!locationLockManager.tryLock(location)) { + messageService.sendMessage(player, "action_in_progress"); + return; + } + + try { + int currentSize = spawner.getStackSize(); + + if (currentSize == 1) { + messageService.sendMessage(player, "spawner_cannot_remove_last"); + return; + } + + int actualChange = currentSize - 1; + + // Cap at available inventory capacity + int inventoryCapacity = countAvailableSpawnerCapacity(player, spawner); + if (inventoryCapacity <= 0) { + messageService.sendMessage(player, "inventory_full"); + return; + } + actualChange = Math.min(actualChange, inventoryCapacity); + int newStackSize = currentSize - actualChange; + + if (SpawnerRemoveEvent.getHandlerList().getRegisteredListeners().length != 0) { + SpawnerRemoveEvent e = new SpawnerRemoveEvent(player, spawner.getSpawnerLocation(), newStackSize, + actualChange); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) + return; + } + + spawner.setStackSize(newStackSize); + spawnerManager.markSpawnerModified(spawner.getSpawnerId()); + + if (spawner.isItemSpawner()) { + giveItemSpawnersToPlayer(player, actualChange, spawner.getSpawnedItemMaterial()); + } else { + giveSpawnersToPlayer(player, actualChange, spawner.getEntityType()); + } + + if (plugin.getSpawnerActionLogger() != null) { + final int logActualChange = actualChange; + final int logNewStackSize = newStackSize; + plugin.getSpawnerActionLogger().log( + github.nighter.smartspawner.logging.SpawnerEventType.SPAWNER_DESTACK_GUI, + builder -> builder.player(player.getName(), player.getUniqueId()) + .location(spawner.getSpawnerLocation()) + .entityType(spawner.getEntityType()) + .metadata("amount_removed", logActualChange) + .metadata("old_stack_size", currentSize) + .metadata("new_stack_size", logNewStackSize)); + } + + player.playSound(player.getLocation(), STACK_SOUND, SOUND_VOLUME, SOUND_PITCH); + } finally { + locationLockManager.unlock(location); + } + } + + /** + * Counts how many spawners of the given type the player's inventory can accept. + * Considers both empty slots and partial stacks of matching spawners. + */ + private int countAvailableSpawnerCapacity(Player player, SpawnerData spawner) { + final int MAX_STACK_SIZE = 64; + int capacity = 0; + ItemStack[] contents = player.getInventory().getContents(); + + for (int i = 0; i < 36; i++) { // Main inventory slots 0-35 + ItemStack item = contents[i]; + if (item == null || item.getType() == Material.AIR) { + capacity += MAX_STACK_SIZE; + continue; + } + + if (item.getType() == Material.SPAWNER && !SpawnerTypeChecker.isVanillaSpawner(item)) { + Optional itemEntityType = getSpawnerEntityTypeCached(item); + boolean matches; + if (spawner.isItemSpawner()) { + // For item spawners, match by spawned item material + matches = false; // Item spawners use separate give method; empty slots cover them + } else { + matches = itemEntityType.isPresent() && itemEntityType.get() == spawner.getEntityType(); + } + + if (matches && item.getAmount() < MAX_STACK_SIZE) { + capacity += MAX_STACK_SIZE - item.getAmount(); + } + } + } + + return capacity; + } + private void scheduleViewersUpdate(SpawnerData spawner) { String spawnerId = spawner.getSpawnerId(); Set viewers = activeViewers.get(spawnerId); @@ -471,6 +657,10 @@ private void updateGui(Player player, SpawnerData spawner) { updateActionButton(inv, "add", STACK_AMOUNTS[i], INCREASE_SLOTS[i], basePlaceholders); } + // Update "all" buttons + updateAllActionButton(inv, "remove_all", REMOVE_ALL_SLOT, basePlaceholders); + updateAllActionButton(inv, "add_all", ADD_ALL_SLOT, basePlaceholders); + // Force client refresh player.updateInventory(); } @@ -525,6 +715,23 @@ private void updateActionButton(Inventory inventory, String action, int amount, button.setItemMeta(meta); } + private void updateAllActionButton(Inventory inventory, String action, int slot, + Map basePlaceholders) { + ItemStack button = inventory.getItem(slot); + if (button == null || !button.hasItemMeta()) + return; + + ItemMeta meta = button.getItemMeta(); + Map placeholders = new HashMap<>(basePlaceholders); + + String name = languageManager.getGuiItemName("button_" + action + ".name", placeholders); + String[] lore = languageManager.getGuiItemLore("button_" + action + ".lore", placeholders); + + meta.setDisplayName(name); + meta.setLore(Arrays.asList(lore)); + button.setItemMeta(meta); + } + // Combined inventory scan that collects all required data in one pass private InventoryScanResult scanPlayerInventory(Player player, EntityType requiredType) { int count = 0; diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerUI.java b/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerUI.java index 30d2806d..1b613a2d 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerUI.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/gui/stacker/SpawnerStackerUI.java @@ -22,6 +22,8 @@ public class SpawnerStackerUI { private static final int[] INCREASE_SLOTS = {17, 16, 15}; private static final int SPAWNER_INFO_SLOT = 13; private static final int[] STACK_AMOUNTS = {64, 10, 1}; + private static final int REMOVE_ALL_SLOT = 22; + private static final int ADD_ALL_SLOT = 4; private final SmartSpawner plugin; private final LanguageManager languageManager; @@ -58,6 +60,8 @@ private void populateStackerGui(Inventory gui, SpawnerData spawner) { gui.setItem(INCREASE_SLOTS[i], createActionButton("add", spawner, STACK_AMOUNTS[i])); } gui.setItem(SPAWNER_INFO_SLOT, createSpawnerInfoButton(spawner)); + gui.setItem(REMOVE_ALL_SLOT, createAllActionButton("remove_all", spawner)); + gui.setItem(ADD_ALL_SLOT, createAllActionButton("add_all", spawner)); } private ItemStack createActionButton(String action, SpawnerData spawner, int amount) { @@ -77,6 +81,15 @@ private ItemStack createSpawnerInfoButton(SpawnerData spawner) { return createButton(Material.SPAWNER, name, lore); } + private ItemStack createAllActionButton(String action, SpawnerData spawner) { + Map placeholders = createPlaceholders(spawner, 0); + String name = languageManager.getGuiItemName("button_" + action + ".name", placeholders); + String[] lore = languageManager.getGuiItemLore("button_" + action + ".lore", placeholders); + Material material = action.equals("add_all") ? Material.LIME_STAINED_GLASS_PANE + : Material.RED_STAINED_GLASS_PANE; + return createButton(material, name, lore); + } + private Map createPlaceholders(SpawnerData spawner, int amount) { Map placeholders = new HashMap<>(); placeholders.put("amount", String.valueOf(amount)); 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 fa2fcd3a..224cfbc7 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 @@ -25,7 +25,7 @@ public class SpawnerData { private String spawnerId; @Getter private final Location spawnerLocation; - + // Fine-grained locks for different operations (Lock Striping Pattern) @Getter private final ReentrantLock inventoryLock = new ReentrantLock(); // For storage operations @@ -65,7 +65,7 @@ public class SpawnerData { private EntityType entityType; @Getter @Setter private EntityLootConfig lootConfig; - + // Item spawner support - stores the material being spawned for item spawners @Getter @Setter private Material spawnedItemMaterial; @@ -115,7 +115,7 @@ public class SpawnerData { // Sort preference for spawner storage @Getter @Setter private Material preferredSortItem; - + // CRITICAL: Pre-generated loot storage for better UX - access must be synchronized via lootGenerationLock private volatile List preGeneratedItems; private volatile int preGeneratedExperience; @@ -174,7 +174,7 @@ public void loadConfigurationValues() { this.spawnDelay = plugin.getTimeFromConfig("spawner_properties.default.delay", "25s"); this.cachedSpawnDelay = (this.spawnDelay + 20L) * 50L; // Add 1 second buffer for GUI display and convert tick to ms this.spawnerRange = plugin.getConfig().getInt("spawner_properties.default.range", 16); - + // Load loot config based on spawner type if (isItemSpawner() && spawnedItemMaterial != null) { this.lootConfig = plugin.getItemSpawnerSettingsConfig().getLootConfig(spawnedItemMaterial); @@ -493,7 +493,10 @@ public void clearInteracted() { public void updateLastInteractedPlayer(String playerName) { this.lastInteractedPlayer = playerName; - markInteracted(); + // Prevent concurrent modification during spawn events to avoid hologram desync + if (System.currentTimeMillis() - lastSpawnTime < 50) { + markInteracted(); + } } /** @@ -721,42 +724,42 @@ public boolean removeItemsAndUpdateSellValue(List items) { inventoryLock.unlock(); } } - + public synchronized void storePreGeneratedLoot(List items, int experience) { this.preGeneratedItems = items; this.preGeneratedExperience = experience; } - + public synchronized List getAndClearPreGeneratedItems() { List items = preGeneratedItems; preGeneratedItems = null; return items; } - + public synchronized int getAndClearPreGeneratedExperience() { int exp = preGeneratedExperience; preGeneratedExperience = 0; return exp; } - + public synchronized boolean hasPreGeneratedLoot() { return (preGeneratedItems != null && !preGeneratedItems.isEmpty()) || preGeneratedExperience > 0; } - + public synchronized void setPreGenerating(boolean generating) { this.isPreGenerating = generating; } - + public synchronized boolean isPreGenerating() { return isPreGenerating; } - + public synchronized void clearPreGeneratedLoot() { preGeneratedItems = null; preGeneratedExperience = 0; isPreGenerating = false; } - + /** * Checks if this is an item spawner (spawns items instead of entities) * @return true if this spawner spawns items diff --git a/core/src/main/java/github/nighter/smartspawner/spawner/sell/SpawnerSellManager.java b/core/src/main/java/github/nighter/smartspawner/spawner/sell/SpawnerSellManager.java index 2c3ac134..3584e419 100644 --- a/core/src/main/java/github/nighter/smartspawner/spawner/sell/SpawnerSellManager.java +++ b/core/src/main/java/github/nighter/smartspawner/spawner/sell/SpawnerSellManager.java @@ -62,19 +62,16 @@ public void sellAllItems(Player player, SpawnerData spawner) { // Get all items for processing Map consolidatedItems = virtualInv.getConsolidatedItems(); - // Process selling async to avoid blocking main thread - Scheduler.runTaskAsync(() -> { - // Use cached sell value for optimization - SellResult result = calculateSellValue(consolidatedItems, spawner); - - // Store the result in SpawnerData for later access - spawner.setLastSellResult(result); - - // Return to main thread for inventory operations and player interaction - Scheduler.runLocationTask(spawner.getSpawnerLocation(), () -> { - processSellResult(player, spawner, result); - }); - }); + + // Process selling synchronously to prevent race conditions + // Use cached sell value for optimization + SellResult result = calculateSellValue(consolidatedItems, spawner); + + // Store the result in SpawnerData for later access + spawner.setLastSellResult(result); + + // Process result immediately on main thread + processSellResult(player, spawner, result); } finally { spawner.getSellLock().unlock(); diff --git a/core/src/main/resources/language/DonutSMP/gui.yml b/core/src/main/resources/language/DonutSMP/gui.yml index 44fca0da..1189a3a3 100644 --- a/core/src/main/resources/language/DonutSMP/gui.yml +++ b/core/src/main/resources/language/DonutSMP/gui.yml @@ -82,6 +82,26 @@ button_add: - '' - 'cc483⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴀᴅᴅ ᴛᴏ ꜱᴛᴀᴄᴋ' +button_remove_all: + name: '&#ff5252ʀᴇᴍᴏᴠᴇ ᴀʟʟ' + lore: + - '' + - '&#e63939◈ &#ff7070ᴀᴄᴛɪᴏɴ:' + - ' &#f8f8ff• ʀᴇᴍᴏᴠᴇ ᴀʟʟ ꜱᴘᴀᴡɴᴇʀꜱ' + - ' &#f8f8ff• ᴄᴜʀʀᴇɴᴛ: &#ff5252{stack_size}' + - '' + - '&#e63939⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ʀᴇᴍᴏᴠᴇ ᴀʟʟ ꜰʀᴏᴍ ꜱᴛᴀᴄᴋ' + +button_add_all: + name: '%eb9aᴀᴅᴅ ᴀʟʟ' + lore: + - '' + - 'cc483◈ 0e89bᴀᴄᴛɪᴏɴ:' + - ' &#f8f8ff• ᴀᴅᴅ ᴀʟʟ ꜱᴘᴀᴡɴᴇʀꜱ ꜰʀᴏᴍ ɪɴᴠᴇɴᴛᴏʀʏ' + - ' &#f8f8ff• ᴄᴜʀʀᴇɴᴛ: %eb9a{stack_size}' + - '' + - 'cc483⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴀᴅᴅ ᴀʟʟ ᴛᴏ ꜱᴛᴀᴄᴋ' + # --------------------------------------------------- # Sell Confirmation GUI # --------------------------------------------------- diff --git a/core/src/main/resources/language/de_DE/gui.yml b/core/src/main/resources/language/de_DE/gui.yml index 08015a42..e43affcf 100644 --- a/core/src/main/resources/language/de_DE/gui.yml +++ b/core/src/main/resources/language/de_DE/gui.yml @@ -163,6 +163,26 @@ button_add: - '' - 'cc483⊳ &#f8f8ffᴋʟɪᴄᴋᴇɴ ᴜᴍ ᴅᴇᴍ ꜱᴛᴀᴄᴋ ʜɪɴᴢᴜᴢᴜꜰᴜᴇɢᴇɴ' +button_remove_all: + name: '&#ff5252ᴀʟʟᴇ ᴇɴᴛꜰᴇʀɴᴇɴ' + lore: + - '' + - '&#e63939◈ &#ff7070ᴀᴋᴛɪᴏɴ:' + - ' &#f8f8ff• ᴀʟʟᴇ ꜱᴘᴀᴡɴᴇʀ ᴇɴᴛꜰᴇʀɴᴇɴ' + - ' &#f8f8ff• ᴀᴋᴛᴜᴇʟʟ: &#ff5252{stack_size}' + - '' + - '&#e63939⊳ &#f8f8ffᴋʟɪᴄᴋᴇɴ ᴜᴍ ᴀʟʟᴇ ᴠᴏᴍ ꜱᴛᴀᴄᴋ ᴢᴜ ᴇɴᴛꜰᴇʀɴᴇɴ' + +button_add_all: + name: '%eb9aᴀʟʟᴇ ʜɪɴᴢᴜꜰᴜᴇɢᴇɴ' + lore: + - '' + - 'cc483◈ 0e89bᴀᴋᴛɪᴏɴ:' + - ' &#f8f8ff• ᴀʟʟᴇ ꜱᴘᴀᴡɴᴇʀ ᴀᴜꜱ ɪɴᴠᴇɴᴛᴀʀ ʜɪɴᴢᴜꜰᴜᴇɢᴇɴ' + - ' &#f8f8ff• ᴀᴋᴛᴜᴇʟʟ: %eb9a{stack_size}' + - '' + - 'cc483⊳ &#f8f8ffᴋʟɪᴄᴋᴇɴ ᴜᴍ ᴀʟʟᴇ ᴅᴇᴍ ꜱᴛᴀᴄᴋ ʜɪɴᴢᴜᴢᴜꜰᴜᴇɢᴇɴ' + # --------------------------------------------------- # Verkaufsbestätigung GUI # --------------------------------------------------- diff --git a/core/src/main/resources/language/en_US/gui.yml b/core/src/main/resources/language/en_US/gui.yml index 8eb3571a..6d683216 100644 --- a/core/src/main/resources/language/en_US/gui.yml +++ b/core/src/main/resources/language/en_US/gui.yml @@ -163,6 +163,26 @@ button_add: - '' - 'cc483⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴀᴅᴅ ᴛᴏ ꜱᴛᴀᴄᴋ' +button_remove_all: + name: '&#ff5252ʀᴇᴍᴏᴠᴇ ᴀʟʟ' + lore: + - '' + - '&#e63939◈ &#ff7070ᴀᴄᴛɪᴏɴ:' + - ' &#f8f8ff• ʀᴇᴍᴏᴠᴇ ᴀʟʟ ꜱᴘᴀᴡɴᴇʀꜱ' + - ' &#f8f8ff• ᴄᴜʀʀᴇɴᴛ: &#ff5252{stack_size}' + - '' + - '&#e63939⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ʀᴇᴍᴏᴠᴇ ᴀʟʟ ꜰʀᴏᴍ ꜱᴛᴀᴄᴋ' + +button_add_all: + name: '%eb9aᴀᴅᴅ ᴀʟʟ' + lore: + - '' + - 'cc483◈ 0e89bᴀᴄᴛɪᴏɴ:' + - ' &#f8f8ff• ᴀᴅᴅ ᴀʟʟ ꜱᴘᴀᴡɴᴇʀꜱ ꜰʀᴏᴍ ɪɴᴠᴇɴᴛᴏʀʏ' + - ' &#f8f8ff• ᴄᴜʀʀᴇɴᴛ: %eb9a{stack_size}' + - '' + - 'cc483⊳ &#f8f8ffᴄʟɪᴄᴋ ᴛᴏ ᴀᴅᴅ ᴀʟʟ ᴛᴏ ꜱᴛᴀᴄᴋ' + # --------------------------------------------------- # Sell Confirmation GUI # --------------------------------------------------- diff --git a/core/src/main/resources/language/vi_VN/gui.yml b/core/src/main/resources/language/vi_VN/gui.yml index 0f30416d..cee3566a 100644 --- a/core/src/main/resources/language/vi_VN/gui.yml +++ b/core/src/main/resources/language/vi_VN/gui.yml @@ -162,6 +162,26 @@ button_add: - '' - 'cc483⊳ &#f8f8ffɴʜấᴘ để ᴛʜêᴍ ᴠàᴏ ꜱᴛᴀᴄᴋ' +button_remove_all: + name: '&#ff5252ɢỡ ᴛấᴛ ᴄả' + lore: + - '' + - '&#e63939◈ &#ff7070ʜàɴʜ độɴɢ:' + - ' &#f8f8ff• ɢỡ ᴛấᴛ ᴄả ꜱᴘᴀᴡɴᴇʀ' + - ' &#f8f8ff• ꜱᴛᴀᴄᴋ ʜɪệɴ ᴛạɪ: &#ff5252{stack_size}' + - '' + - '&#e63939⊳ &#f8f8ffɴʜấᴘ để ɢỡ ᴛấᴛ ᴄả ᴋʜỏɪ ꜱᴛᴀᴄᴋ' + +button_add_all: + name: '%eb9aᴛʜêᴍ ᴛấᴛ ᴄả' + lore: + - '' + - 'cc483◈ 0e89bʜàɴʜ độɴɢ:' + - ' &#f8f8ff• ᴛʜêᴍ ᴛấᴛ ᴄả ꜱᴘᴀᴡɴᴇʀ ᴛừ ᴛúɪ đồ' + - ' &#f8f8ff• ꜱᴛᴀᴄᴋ ʜɪệɴ ᴛạɪ: %eb9a{stack_size}' + - '' + - 'cc483⊳ &#f8f8ffɴʜấᴘ để ᴛʜêᴍ ᴛấᴛ ᴄả ᴠàᴏ ꜱᴛᴀᴄᴋ' + # --------------------------------------------------- # GUI Xác Nhận Bán # ---------------------------------------------------