Skip to content

Commit 89376cb

Browse files
committed
feat: implement withdraw functionality for currency management and enhance GUI click handling
1 parent e4033b4 commit 89376cb

9 files changed

Lines changed: 259 additions & 38 deletions

File tree

core/src/main/java/github/nighter/smartspawner/hooks/economy/ItemPriceManager.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -394,15 +394,6 @@ private String determineActiveSource(double customPrice, double shopPrice) {
394394
};
395395
}
396396

397-
public boolean deposit(double amount, OfflinePlayer player) {
398-
if (!economyEnabled || currencyManager == null) {
399-
plugin.getLogger().warning("Economy is not enabled or currency manager is not initialized.");
400-
return false;
401-
}
402-
403-
return currencyManager.deposit(amount, player);
404-
}
405-
406397
private void saveConfig() {
407398
if (!economyEnabled) return;
408399

core/src/main/java/github/nighter/smartspawner/hooks/economy/currency/CurrencyManager.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,34 @@ public boolean deposit(double amount, OfflinePlayer player) {
144144
return false;
145145
}
146146

147+
public void withdraw(double amount, OfflinePlayer player) {
148+
if (!currencyAvailable) {
149+
plugin.getLogger().warning("Currency not available for withdraw operation.");
150+
return;
151+
}
152+
153+
if (configuredCurrencyType.equalsIgnoreCase("VAULT")) {
154+
if (vaultEconomy == null) {
155+
plugin.getLogger().warning("Vault economy is not initialized.");
156+
return;
157+
}
158+
vaultEconomy.withdrawPlayer(player, amount).transactionSuccess();
159+
return;
160+
}
161+
162+
if (configuredCurrencyType.equalsIgnoreCase("COINSENGINE")) {
163+
if (coinsEngineCurrency == null) {
164+
plugin.getLogger().warning("CoinsEngine currency is not initialized.");
165+
return;
166+
}
167+
168+
CoinsEngineAPI.removeBalance(player.getUniqueId(), coinsEngineCurrency, amount);
169+
return;
170+
}
171+
172+
plugin.getLogger().warning("Unsupported currency type during withdraw: " + configuredCurrencyType);
173+
}
174+
147175
public void reload() {
148176
// Clean up existing connections
149177
cleanup();

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

Lines changed: 196 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.bukkit.event.EventHandler;
2121
import org.bukkit.event.EventPriority;
2222
import org.bukkit.event.Listener;
23+
import org.bukkit.event.inventory.ClickType;
2324
import org.bukkit.event.inventory.InventoryClickEvent;
2425
import org.bukkit.event.inventory.InventoryCloseEvent;
2526
import org.bukkit.event.inventory.InventoryDragEvent;
@@ -50,7 +51,9 @@ public class SpawnerStorageAction implements Listener {
5051

5152
private record TransferResult(boolean anyItemMoved, boolean inventoryFull, int totalMoved) {}
5253
private final Map<UUID, Long> lastItemClickTime = new ConcurrentHashMap<>();
53-
private static final long CLICK_DELAY_MS = 300;
54+
private final Map<UUID, Long> lastControlClickTime = new ConcurrentHashMap<>();
55+
private static final long ITEM_CLICK_DELAY_MS = 150;
56+
private static final long CONTROL_CLICK_DELAY_MS = 300;
5457
private final Random random = new Random();
5558
private GuiLayout layout;
5659

@@ -71,7 +74,7 @@ public void loadConfig() {
7174
}
7275

7376

74-
@EventHandler(priority = EventPriority.HIGH)
77+
@EventHandler(priority = EventPriority.LOWEST)
7578
public void onInventoryClick(InventoryClickEvent event) {
7679
if (!(event.getWhoClicked() instanceof Player player) ||
7780
!(event.getInventory().getHolder(false) instanceof StoragePageHolder holder)) {
@@ -80,14 +83,20 @@ public void onInventoryClick(InventoryClickEvent event) {
8083

8184
SpawnerData spawner = holder.getSpawnerData();
8285
int slot = event.getRawSlot();
83-
84-
// Cancel event immediately to prevent any vanilla behavior
8586
event.setCancelled(true);
8687

88+
// Handle clicks outside valid storage GUI area
8789
if (slot < 0 || slot >= INVENTORY_SIZE) {
8890
return;
8991
}
9092

93+
// Handle item slot clicks (taking items from storage)
94+
if (isItemSlot(slot)) {
95+
handleItemSlotClick(player, slot, holder, spawner, event);
96+
return;
97+
}
98+
99+
// Handle control button clicks
91100
if (isControlSlot(slot)) {
92101
handleControlSlotClick(player, slot, holder, spawner, event.getInventory(), layout);
93102
}
@@ -131,7 +140,7 @@ private void handleControlSlotClick(Player player, int slot, StoragePageHolder h
131140
messageService.sendMessage(player, "no_permission");
132141
return;
133142
}
134-
if (isClickTooFrequent(player)) {
143+
if (isControlClickTooFrequent(player)) {
135144
return;
136145
}
137146
// Check if there are items to sell
@@ -154,8 +163,171 @@ private boolean isControlSlot(int slot) {
154163
return layout != null && layout.isSlotUsed(slot);
155164
}
156165

166+
private boolean isItemSlot(int slot) {
167+
// First 45 slots (0-44) are for storage items
168+
return slot >= 0 && slot < STORAGE_SLOTS && !isControlSlot(slot);
169+
}
170+
171+
/**
172+
* Handles clicks on item slots in the storage GUI.
173+
* ALL clicks transfer items directly to player inventory (no cursor interaction).
174+
* - LEFT CLICK: Take 1 item from stack
175+
* - RIGHT CLICK: Take half of stack
176+
* - SHIFT CLICK: Take entire stack
177+
*/
178+
private void handleItemSlotClick(Player player, int slot, StoragePageHolder holder,
179+
SpawnerData spawner, InventoryClickEvent event) {
180+
// Anti-spam check
181+
if (isItemClickTooFrequent(player)) {
182+
return;
183+
}
184+
185+
Inventory inventory = event.getInventory();
186+
ItemStack clickedItem = inventory.getItem(slot);
187+
188+
// Nothing to take from empty slot
189+
if (clickedItem == null || clickedItem.getType() == Material.AIR) {
190+
return;
191+
}
192+
193+
// Determine amount to take based on click type
194+
ClickType clickType = event.getClick();
195+
int amountToTake;
196+
197+
switch (clickType) {
198+
case LEFT:
199+
// Left click = take only 1 item
200+
amountToTake = 1;
201+
break;
202+
case RIGHT:
203+
// Right click = take half
204+
amountToTake = (int) Math.ceil(clickedItem.getAmount() / 2.0);
205+
break;
206+
case SHIFT_LEFT:
207+
case SHIFT_RIGHT:
208+
// Shift click = take all
209+
amountToTake = clickedItem.getAmount();
210+
break;
211+
default:
212+
// Ignore other click types
213+
return;
214+
}
215+
216+
// Transfer items to player inventory
217+
transferToPlayerInventory(player, clickedItem, amountToTake, inventory, spawner, holder);
218+
}
219+
220+
/**
221+
* Optimized method to transfer items from storage to player inventory.
222+
* Handles all click types with a single efficient path.
223+
*/
224+
private void transferToPlayerInventory(Player player, ItemStack clickedItem, int amountToTake,
225+
Inventory storageInv, SpawnerData spawner, StoragePageHolder holder) {
226+
PlayerInventory playerInv = player.getInventory();
227+
ItemStack toTransfer = clickedItem.clone();
228+
toTransfer.setAmount(amountToTake);
229+
230+
int amountMoved = 0;
231+
int remaining = amountToTake;
232+
233+
// Optimize: Try to stack with existing items first (more efficient)
234+
for (int i = 0; i < 36 && remaining > 0; i++) {
235+
ItemStack slot = playerInv.getItem(i);
236+
237+
if (slot != null && slot.getType() != Material.AIR && slot.isSimilar(toTransfer)) {
238+
// Found similar item - try to stack
239+
int space = slot.getMaxStackSize() - slot.getAmount();
240+
if (space > 0) {
241+
int add = Math.min(space, remaining);
242+
slot.setAmount(slot.getAmount() + add);
243+
amountMoved += add;
244+
remaining -= add;
245+
}
246+
}
247+
}
248+
249+
// Then fill empty slots
250+
for (int i = 0; i < 36 && remaining > 0; i++) {
251+
ItemStack slot = playerInv.getItem(i);
252+
253+
if (slot == null || slot.getType() == Material.AIR) {
254+
int stackSize = Math.min(remaining, toTransfer.getMaxStackSize());
255+
ItemStack newStack = toTransfer.clone();
256+
newStack.setAmount(stackSize);
257+
playerInv.setItem(i, newStack);
258+
amountMoved += stackSize;
259+
remaining -= stackSize;
260+
}
261+
}
262+
263+
// Update VirtualInventory if any items were moved
264+
if (amountMoved > 0) {
265+
ItemStack removed = toTransfer.clone();
266+
removed.setAmount(amountMoved);
267+
268+
if (spawner.removeItemsAndUpdateSellValue(List.of(removed))) {
269+
// Update display efficiently
270+
updatePageAfterRemoval(player, storageInv, spawner, holder);
271+
272+
// Single sound effect
273+
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.5f, 1.0f);
274+
275+
// Notify if inventory was full
276+
if (remaining > 0) {
277+
messageService.sendMessage(player, "inventory_full");
278+
}
279+
}
280+
} else {
281+
// No items moved - inventory full
282+
messageService.sendMessage(player, "inventory_full");
283+
}
284+
}
285+
286+
287+
/**
288+
* Updates the page display after items are removed from storage.
289+
*/
290+
private void updatePageAfterRemoval(Player player, Inventory inventory,
291+
SpawnerData spawner, StoragePageHolder holder) {
292+
// Recalculate pages
293+
int newTotalPages = calculateTotalPages(spawner);
294+
int currentPage = holder.getCurrentPage();
295+
296+
// Clamp to valid page range
297+
int adjustedPage = Math.max(1, Math.min(currentPage, newTotalPages));
298+
299+
holder.setTotalPages(newTotalPages);
300+
if (adjustedPage != currentPage) {
301+
holder.setCurrentPage(adjustedPage);
302+
}
303+
holder.updateOldUsedSlots();
304+
305+
// Update display
306+
SpawnerStorageUI spawnerStorageUI = plugin.getSpawnerStorageUI();
307+
spawnerStorageUI.updateDisplay(inventory, spawner, adjustedPage, newTotalPages);
308+
309+
// Update title if pages changed
310+
if (newTotalPages != currentPage || adjustedPage != currentPage) {
311+
updateInventoryTitle(player, spawner, adjustedPage, newTotalPages);
312+
}
313+
314+
// Update hologram and other viewers
315+
spawner.updateHologramData();
316+
spawnerGuiViewManager.updateSpawnerMenuViewers(spawner);
317+
318+
// Check capacity
319+
if (spawner.getMaxSpawnerLootSlots() > holder.getOldUsedSlots() && spawner.getIsAtCapacity()) {
320+
spawner.setIsAtCapacity(false);
321+
}
322+
323+
// Mark as modified
324+
if (!spawner.isInteracted()) {
325+
spawner.markInteracted();
326+
}
327+
}
328+
157329
private void handleDropPageItems(Player player, SpawnerData spawner, Inventory inventory) {
158-
if (isClickTooFrequent(player)) {
330+
if (isControlClickTooFrequent(player)) {
159331
return;
160332
}
161333

@@ -259,7 +431,7 @@ private void dropItemsInDirection(Player player, List<ItemStack> items) {
259431

260432

261433
private void openFilterConfig(Player player, SpawnerData spawner) {
262-
if (isClickTooFrequent(player)) {
434+
if (isControlClickTooFrequent(player)) {
263435
return;
264436
}
265437
filterConfigUI.openFilterConfigGUI(player, spawner);
@@ -304,17 +476,30 @@ private void updateInventoryTitle(Player player, SpawnerData spawner, int page,
304476
}
305477
}
306478

307-
private boolean isClickTooFrequent(Player player) {
479+
private boolean isItemClickTooFrequent(Player player) {
308480
long now = System.currentTimeMillis();
309481
long last = lastItemClickTime.getOrDefault(player.getUniqueId(), 0L);
310482
lastItemClickTime.put(player.getUniqueId(), now);
311-
return (now - last) < CLICK_DELAY_MS;
483+
484+
if ((now - last) < ITEM_CLICK_DELAY_MS) {
485+
messageService.sendMessage(player, "click_too_fast");
486+
return true;
487+
}
488+
return false;
489+
}
490+
491+
private boolean isControlClickTooFrequent(Player player) {
492+
long now = System.currentTimeMillis();
493+
long last = lastControlClickTime.getOrDefault(player.getUniqueId(), 0L);
494+
lastControlClickTime.put(player.getUniqueId(), now);
495+
return (now - last) < CONTROL_CLICK_DELAY_MS;
312496
}
313497

314498
@EventHandler
315499
public void onPlayerQuit(PlayerQuitEvent event) {
316500
UUID playerId = event.getPlayer().getUniqueId();
317501
lastItemClickTime.remove(playerId);
502+
lastControlClickTime.remove(playerId);
318503
}
319504

320505
private void openMainMenu(Player player, SpawnerData spawner) {
@@ -352,7 +537,7 @@ private boolean isBedrockPlayer(Player player) {
352537
}
353538

354539
private void handleSortItemsClick(Player player, SpawnerData spawner, Inventory inventory) {
355-
if (isClickTooFrequent(player)) {
540+
if (isControlClickTooFrequent(player)) {
356541
return;
357542
}
358543

@@ -455,7 +640,7 @@ private void openLootPage(Player player, SpawnerData spawner, int page) {
455640
}
456641

457642
public void handleTakeAllItems(Player player, Inventory sourceInventory) {
458-
if (isClickTooFrequent(player)) {
643+
if (isControlClickTooFrequent(player)) {
459644
return;
460645
}
461646
StoragePageHolder holder = (StoragePageHolder) sourceInventory.getHolder(false);

core/src/main/java/github/nighter/smartspawner/spawner/gui/synchronization/managers/ViewerTrackingManager.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package github.nighter.smartspawner.spawner.gui.synchronization.managers;
22

33
import github.nighter.smartspawner.spawner.properties.SpawnerData;
4+
import lombok.Getter;
45
import org.bukkit.Bukkit;
56
import org.bukkit.entity.Player;
67

@@ -54,7 +55,14 @@ public ViewerType getViewerType() {
5455
private final Map<UUID, ViewerInfo> playerToSpawnerMap = new ConcurrentHashMap<>();
5556
private final Map<String, Set<UUID>> spawnerToPlayersMap = new ConcurrentHashMap<>();
5657

58+
/**
59+
* -- GETTER --
60+
* Gets all main menu viewers.
61+
*
62+
* @return Map of player UUIDs to their viewer info
63+
*/
5764
// Track main menu viewers separately for timer updates
65+
@Getter
5866
private final Map<UUID, ViewerInfo> mainMenuViewers = new ConcurrentHashMap<>();
5967
private final Map<String, Set<UUID>> spawnerToMainMenuViewers = new ConcurrentHashMap<>();
6068

@@ -177,15 +185,6 @@ public ViewerInfo getViewerInfo(UUID playerId) {
177185
return playerToSpawnerMap.get(playerId);
178186
}
179187

180-
/**
181-
* Gets all main menu viewers.
182-
*
183-
* @return Map of player UUIDs to their viewer info
184-
*/
185-
public Map<UUID, ViewerInfo> getMainMenuViewers() {
186-
return mainMenuViewers;
187-
}
188-
189188
/**
190189
* Gets main menu viewers for a specific spawner.
191190
*

0 commit comments

Comments
 (0)