Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UUID, Long> lastClickTime = new ConcurrentHashMap<>(16, 0.75f, 2);
Expand Down Expand Up @@ -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<UUID> 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<UUID> viewers = activeViewers.get(spawnerId);
if (viewers != null && !viewers.isEmpty()) {
scheduleViewersUpdate(spawner);
}
return;
}

int changeAmount = determineChangeAmount(slotIndex);

if (changeAmount != 0) {
Expand All @@ -157,7 +182,7 @@ public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getInventory().getHolder(false) instanceof SpawnerStackerHolder holder)) {
return;
}

event.setCancelled(true);
}

Expand Down Expand Up @@ -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<String, String> 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<String, String> 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<EntityType> 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<UUID> viewers = activeViewers.get(spawnerId);
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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<String, String> basePlaceholders) {
ItemStack button = inventory.getItem(slot);
if (button == null || !button.hasItemMeta())
return;

ItemMeta meta = button.getItemMeta();
Map<String, String> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -77,6 +81,15 @@ private ItemStack createSpawnerInfoButton(SpawnerData spawner) {
return createButton(Material.SPAWNER, name, lore);
}

private ItemStack createAllActionButton(String action, SpawnerData spawner) {
Map<String, String> 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<String, String> createPlaceholders(SpawnerData spawner, int amount) {
Map<String, String> placeholders = new HashMap<>();
placeholders.put("amount", String.valueOf(amount));
Expand Down
Loading