From 902f8ed43c3344d188e18b41d268ab23016fbaa3 Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Thu, 30 Apr 2026 09:34:22 +0200 Subject: [PATCH] Upload folder via GUI - src --- .../de/mviper/spigot/IngameShopSpigot.java | 393 ++++++++++++++++-- 1 file changed, 363 insertions(+), 30 deletions(-) diff --git a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java index 44c222a..b1dc9ba 100644 --- a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java +++ b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java @@ -92,6 +92,12 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private Map orderCache = new HashMap<>(); private Map activeOrderIds = new ConcurrentHashMap<>(); + // Gift-System: Ausstehende Geschenk-Anfragen (Empfänger-UUID → OrderData) + private final Map pendingGiftRequests = new ConcurrentHashMap<>(); + // URL für Geschenk-Endpoint und Rückerstattungs-Endpoint + private String wpUrlGiftAccept; + private String wpUrlGiftDecline; + private FlyManager flyManager; private FlyCodeManager flyCodeManager; private RankManager rankManager; @@ -134,6 +140,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { wpUrlCancel = wpBase + "/cancel_order"; wpUrlSellItems = wpBase + "/sell_items"; wpUrlSell = wpBase + "/sell_item"; + wpUrlGiftAccept = wpBase + "/gift_accept"; + wpUrlGiftDecline = wpBase + "/gift_decline"; targetServer = getConfig().getString("server-name", "survival").toLowerCase(); currency = getConfig().getString("currency-name", "Coins"); @@ -280,7 +288,6 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { "Spieler " + p.getName() + " hat aktive Order – überspringe."); continue; } - // Join-Fetch läuft noch – nicht parallel pollen Long cooldown = joinFetchCooldown.get(p.getUniqueId()); if (cooldown != null && System.currentTimeMillis() < cooldown) { if (debug) getLogger().info( @@ -328,20 +335,27 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { String itemTitle = order.get("item_title").getAsString(); double price = order.get("price").getAsDouble(); - // Atomar: nur wenn noch keine aktive Order → eintragen und GUI öffnen - // synchronized auf activeOrderIds verhindert Race Condition zwischen zwei Async-Threads + // Gift-Empfänger auslesen – leer bei normalen Käufen + String giftRecipient = (order.has("gift_recipient") + && !order.get("gift_recipient").isJsonNull()) + ? order.get("gift_recipient").getAsString().trim() : ""; + + // Spieler A bestätigt IMMER zuerst selbst (verhindert Missbrauch). + // giftRecipient wird in OrderData gespeichert und erst nach + // Bestätigung + Geld-Abbuchung an Spieler B weitergeleitet. synchronized (activeOrderIds) { if (activeOrderIds.containsKey(p.getUniqueId())) { if (debug) getLogger().info( "Order #" + id + " für " + p.getName() + " ignoriert (bereits aktiv)."); return; } - OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse); + OrderData data = new OrderData(id, "multi_item", itemTitle, + price, 1, jsonResponse, giftRecipient); orderCache.put(id, data); activeOrderIds.put(p.getUniqueId(), id); } Bukkit.getScheduler().runTask(IngameShopSpigot.this, - () -> openConfirmGUI(p, id, itemTitle, price, jsonResponse)); + () -> openConfirmGUI(p, id, itemTitle, price, jsonResponse, giftRecipient)); } } else if (responseCode == 401) { getLogger().warning("❌ API-Key ungültig! HTTP 401 von " + endpointUrl); @@ -351,6 +365,224 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // =========================================================== + // GIFT-SYSTEM + // =========================================================== + + /** + * Wird aufgerufen wenn eine Order ein gift_recipient-Feld hat. + * Leitet die Anfrage an den Empfänger (Spieler B) weiter. + * Falls Spieler B offline → Order bleibt pending (wird beim nächsten Join geholt). + */ + private void handleIncomingGift(String senderName, String recipientName, + int orderId, String itemTitle, + double price, String jsonResponse) { + Player recipient = Bukkit.getPlayerExact(recipientName); + if (recipient == null || !recipient.isOnline()) { + // Empfänger offline – Order bleibt pending, beim Join wird sie erneut gepollt + if (debug) getLogger().info( + "[Gift] Empfänger " + recipientName + " offline – warte auf Join."); + return; + } + + // Empfänger bereits mit offener Gift-Anfrage? + if (pendingGiftRequests.containsKey(recipient.getUniqueId())) { + if (debug) getLogger().info( + "[Gift] " + recipientName + " hat bereits eine offene Gift-Anfrage."); + return; + } + + GiftRequest req = new GiftRequest(orderId, senderName, recipientName, + itemTitle, price, jsonResponse); + pendingGiftRequests.put(recipient.getUniqueId(), req); + orderCache.put(orderId, new OrderData(orderId, "multi_item", itemTitle, price, 1, jsonResponse)); + + // Order auf WP auf 'processing' setzen damit sie nicht nochmal gepollt wird + // (A hat bereits bezahlt, B muss nur noch annehmen/ablehnen) + new BukkitRunnable() { + @Override public void run() { + try { + HttpURLConnection conn = openAuthConnection(wpUrlExecute, "POST"); + writeJson(conn, "{\"id\":\"" + orderId + "\"}"); + conn.getResponseCode(); + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "[Gift] Status-Update Fehler", e); + } + } + }.runTaskAsynchronously(this); + + Bukkit.getScheduler().runTask(this, () -> openGiftConfirmGUI(recipient, req)); + } + + /** Öffnet dem Empfänger das Gift-Bestätigungs-GUI */ + private void openGiftConfirmGUI(Player recipient, GiftRequest req) { + Inventory gui = Bukkit.createInventory(null, 27, GUI_GIFT_TITLE); + + ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE); + ItemMeta pM = pane.getItemMeta(); + pM.setDisplayName(" "); + pane.setItemMeta(pM); + for (int i = 0; i < 27; i++) gui.setItem(i, pane); + + // Info-Item in der Mitte + Material icon = getFirstItemMaterial(req.jsonPayload); + ItemStack info = new ItemStack(icon); + ItemMeta infoMeta = info.getItemMeta(); + infoMeta.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + req.itemTitle); + infoMeta.setLore(Arrays.asList( + ChatColor.GRAY + "──────────────────", + ChatColor.WHITE + "Von: " + ChatColor.YELLOW + req.senderName, + ChatColor.WHITE + "Wert: " + ChatColor.YELLOW + req.price + " " + currency, + ChatColor.GRAY + "──────────────────", + ChatColor.GREEN + "🎁 Du hast ein Geschenk erhalten!", + ChatColor.GRAY + "Annehmen oder ablehnen?" + )); + info.setItemMeta(infoMeta); + + // Annehmen + ItemStack yes = new ItemStack(Material.LIME_WOOL); + ItemMeta yM = yes.getItemMeta(); + yM.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "✔ Annehmen"); + yM.setLore(Arrays.asList(ChatColor.GRAY + "Geschenk wird dir übergeben")); + yes.setItemMeta(yM); + + // Ablehnen + ItemStack no = new ItemStack(Material.RED_WOOL); + ItemMeta nM = no.getItemMeta(); + nM.setDisplayName(ChatColor.RED + "" + ChatColor.BOLD + "✘ Ablehnen"); + nM.setLore(Arrays.asList( + ChatColor.GRAY + "Bestellung wird storniert,", + ChatColor.GRAY + req.senderName + " erhält sein Geld zurück" + )); + no.setItemMeta(nM); + + gui.setItem(13, info); + gui.setItem(11, yes); + gui.setItem(15, no); + recipient.openInventory(gui); + } + + /** Empfänger nimmt Geschenk an → Ware ausliefern */ + private void acceptGift(Player recipient, GiftRequest req) { + // pendingGiftRequests wurde bereits im Click-Handler entfernt (vor closeInventory) + + // "__GIFT_DELIVERY__" signalisiert executeShopLogic: kein Geldabzug, kein incomeReceiver + // (Geld wurde bereits bei A's Bestätigung abgezogen) + final OrderData deliverData = new OrderData(req.orderId, "multi_item", req.itemTitle, + req.price, 1, req.jsonPayload, "__GIFT_DELIVERY__"); + + recipient.sendMessage(ChatColor.GREEN + "🎁 Du hast das Geschenk von " + + ChatColor.YELLOW + req.senderName + + ChatColor.GREEN + " angenommen!"); + + // Ware direkt ausliefern (Geld wurde bereits von Käufer A abgezogen) + // executeShopLogic ruft intern markOrderCompleted auf → WP-Status wird 'completed' + executeShopLogic(recipient, deliverData, req.orderId); + + // Sender benachrichtigen falls online + Player sender = Bukkit.getPlayerExact(req.senderName); + if (sender != null && sender.isOnline()) { + sender.sendMessage(ChatColor.GREEN + "🎁 " + ChatColor.YELLOW + recipient.getName() + + ChatColor.GREEN + " hat dein Geschenk angenommen!"); + sender.playSound(sender.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F); + } + } + + /** Empfänger lehnt Geschenk ab → Stornierung + Rückerstattung an Käufer */ + private void declineGift(Player recipient, GiftRequest req) { + pendingGiftRequests.remove(recipient.getUniqueId()); + orderCache.remove(req.orderId); + + recipient.sendMessage(ChatColor.YELLOW + "❌ Geschenk abgelehnt."); + + new BukkitRunnable() { + @Override public void run() { + try { + // 1) Order auf WordPress stornieren + HttpURLConnection conn = openAuthConnection(wpUrlGiftDecline, "POST"); + writeJson(conn, "{\"id\":\"" + req.orderId + "\"" + + ",\"sender\":\"" + req.senderName + "\"" + + ",\"price\":" + (int) req.price + "}"); + int code = conn.getResponseCode(); + if (debug) getLogger().info( + "[Gift] Decline HTTP " + code + " für Order #" + req.orderId); + + // 2) Rückerstattung an Käufer (Vault) + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + @SuppressWarnings("deprecation") + org.bukkit.OfflinePlayer buyer = + Bukkit.getOfflinePlayerIfCached(req.senderName); + if (buyer == null) { + for (org.bukkit.OfflinePlayer op : Bukkit.getOfflinePlayers()) { + if (op.getName() != null + && op.getName().equalsIgnoreCase(req.senderName)) { + buyer = op; + break; + } + } + } + if (buyer != null) { + econ.depositPlayer(buyer, req.price); + if (debug) getLogger().info( + "[Gift] Rückerstattung " + req.price + " " + currency + + " an " + req.senderName); + } + + // Käufer benachrichtigen falls online + Player senderOnline = Bukkit.getPlayerExact(req.senderName); + if (senderOnline != null && senderOnline.isOnline()) { + senderOnline.sendMessage(ChatColor.RED + + "🎁 " + ChatColor.YELLOW + recipient.getName() + + ChatColor.RED + " hat dein Geschenk abgelehnt."); + senderOnline.sendMessage(ChatColor.GREEN + + "💰 " + req.price + " " + currency + + " wurden dir erstattet."); + } + }); + } catch (Exception e) { + getLogger().log(Level.WARNING, "[Gift] Fehler bei decline", e); + } + } + }.runTaskAsynchronously(this); + } + + // Gift-GUI Polling starten (Empfänger-Polling beim Join) + private void checkGiftOrdersForPlayer(Player p) { + new BukkitRunnable() { + @Override public void run() { + try { + HttpURLConnection conn = openAuthConnection( + wpBase + "/pending_gifts?recipient=" + p.getName(), "GET"); + int code = conn.getResponseCode(); + if (code != 200) return; + String body = readResponse(conn); + JsonObject json = JsonParser.parseString(body).getAsJsonObject(); + JsonArray orders = json.getAsJsonArray("orders"); + if (orders == null || orders.size() == 0) return; + + JsonObject order = orders.get(0).getAsJsonObject(); + int id = order.get("id").getAsInt(); + String senderName = order.has("player_name") + ? order.get("player_name").getAsString() : "?"; + String orderServer = order.has("server") + ? order.get("server").getAsString().toLowerCase() : ""; + if (!orderServer.equals(targetServer)) return; + + String jsonResponse = order.has("response") + ? order.get("response").getAsString() : "[]"; + String itemTitle = order.get("item_title").getAsString(); + double price = order.get("price").getAsDouble(); + + handleIncomingGift(senderName, p.getName(), id, itemTitle, price, jsonResponse); + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "[Gift] Polling Fehler", e); + } + } + }.runTaskAsynchronously(this); + } + + private static final String GUI_GIFT_TITLE = ChatColor.GOLD + "🎁 Geschenk erhalten!"; + // =========================================================== // OFFLINE-QUEUE & FLY-RESTORE // =========================================================== @@ -394,6 +626,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { }.runTaskAsynchronously(this); } + // Ausstehende Gift-Orders prüfen (Spieler B ist jetzt online) + new BukkitRunnable() { + @Override public void run() { checkGiftOrdersForPlayer(p); } + }.runTaskAsynchronously(this); + // Ausstehende Fly-Codes anzeigen new BukkitRunnable() { @Override public void run() { @@ -464,8 +701,21 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { if (!(event.getPlayer() instanceof Player)) return; Player p = (Player) event.getPlayer(); - // Confirm-GUI: bei ESC sofort wieder öffnen + // Titel zuerst holen – wird für beide GUI-Checks benötigt String title = event.getView().getTitle(); + + // Gift-Confirm-GUI: bei ESC sofort wieder öffnen + if (GUI_GIFT_TITLE.equals(title) && pendingGiftRequests.containsKey(p.getUniqueId())) { + GiftRequest req = pendingGiftRequests.get(p.getUniqueId()); + if (req != null) { + p.sendMessage(ChatColor.YELLOW + "⚠ Bitte nimm das Geschenk an oder lehne es ab!"); + Bukkit.getScheduler().runTaskLater(this, + () -> openGiftConfirmGUI(p, req), 2L); + return; + } + } + + // Confirm-GUI: bei ESC sofort wieder öffnen if (GUI_CONFIRM_TITLE.equals(title) && activeOrderIds.containsKey(p.getUniqueId())) { int orderId = activeOrderIds.get(p.getUniqueId()); OrderData data = orderCache.get(orderId); @@ -494,24 +744,40 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private void openConfirmGUI(Player player, int orderId, String itemTitle, double price, String jsonPayload) { + openConfirmGUI(player, orderId, itemTitle, price, jsonPayload, ""); + } + + private void openConfirmGUI(Player player, int orderId, + String itemTitle, double price, + String jsonPayload, String giftRecipient) { Inventory gui = Bukkit.createInventory(null, 27, GUI_CONFIRM_TITLE); Material icon = getFirstItemMaterial(jsonPayload); + boolean isGift = giftRecipient != null && !giftRecipient.isEmpty(); + ItemStack info = new ItemStack(icon); ItemMeta infoMeta = info.getItemMeta(); - infoMeta.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + itemTitle); - infoMeta.setLore(Arrays.asList( + infoMeta.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + + (isGift ? "🎁 " : "") + itemTitle); + List lore = new ArrayList<>(Arrays.asList( ChatColor.GRAY + "──────────────────", - ChatColor.WHITE + "Preis: " + ChatColor.YELLOW + price + " " + currency, - ChatColor.GRAY + "──────────────────", - ChatColor.GRAY + "Klicke Grün zum Bestätigen", - ChatColor.GRAY + "Klicke Rot zum Abbrechen" + ChatColor.WHITE + "Preis: " + ChatColor.YELLOW + price + " " + currency )); + if (isGift) { + lore.add(ChatColor.LIGHT_PURPLE + "Geschenk für: " + + ChatColor.WHITE + giftRecipient); + lore.add(ChatColor.GRAY + "Der Empfänger muss ingame annehmen."); + } + lore.add(ChatColor.GRAY + "──────────────────"); + lore.add(ChatColor.GRAY + "Klicke Grün zum Bestätigen"); + lore.add(ChatColor.GRAY + "Klicke Rot zum Abbrechen"); + infoMeta.setLore(lore); info.setItemMeta(infoMeta); ItemStack yes = new ItemStack(Material.LIME_WOOL); ItemMeta yM = yes.getItemMeta(); - yM.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "✔ JA, kaufen!"); + yM.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + + (isGift ? "🎁 JA, verschenken!" : "✔ JA, kaufen!")); yM.setLore(Arrays.asList( ChatColor.GRAY + "" + price + " " + currency + " werden abgezogen")); yes.setItemMeta(yM); @@ -565,6 +831,28 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { public void onInventoryClick(InventoryClickEvent event) { String title = event.getView().getTitle(); + // ── Gift-Bestätigen GUI ──────────────────────────────────────────── + if (GUI_GIFT_TITLE.equals(title)) { + event.setCancelled(true); + if (!(event.getWhoClicked() instanceof Player)) return; + Player p = (Player) event.getWhoClicked(); + int slot = event.getRawSlot(); + GiftRequest req = pendingGiftRequests.get(p.getUniqueId()); + if (req == null) { p.closeInventory(); return; } + if (slot == 11) { + // ERST aus pendingGiftRequests entfernen, DANN closeInventory – + // sonst triggert onInventoryClose das GUI erneut + pendingGiftRequests.remove(p.getUniqueId()); + p.closeInventory(); + acceptGift(p, req); + } else if (slot == 15) { + pendingGiftRequests.remove(p.getUniqueId()); + p.closeInventory(); + declineGift(p, req); + } + return; + } + // ── Sell GUI ────────────────────────────────────────────────────── if (handleSellClick(event)) return; @@ -840,18 +1128,21 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private void executeShopLogic(Player player, OrderData data, int orderId) { try { - if (econ.getBalance(player) < data.price) { - player.sendMessage(ChatColor.RED + "❌ Nicht genug " - + currency + "! (Benötigt: " + data.price + ")"); - cancelOrder(player, orderId); - return; + // Bei Geschenk-Lieferung an B: kein Geldabzug – Geld wurde bereits von A abgezogen + boolean isGiftDelivery = data.giftRecipient != null && data.giftRecipient.equals("__GIFT_DELIVERY__"); + if (!isGiftDelivery) { + if (econ.getBalance(player) < data.price) { + player.sendMessage(ChatColor.RED + "❌ Nicht genug " + + currency + "! (Benötigt: " + data.price + ")"); + cancelOrder(player, orderId); + return; + } + econ.withdrawPlayer(player, data.price); + player.sendMessage(ChatColor.GREEN + "💰 " + data.price + " " + currency + " abgezogen."); } - econ.withdrawPlayer(player, data.price); - player.sendMessage(ChatColor.GREEN + "💰 " + data.price + " " + currency + " abgezogen."); - - // Einnahmen an Empfänger-Account gutschreiben - if (!incomeReceiver.isEmpty()) { + // Einnahmen an Empfänger-Account gutschreiben (nicht bei Gift-Lieferung – bereits bei A gebucht) + if (!isGiftDelivery && !incomeReceiver.isEmpty()) { try { @SuppressWarnings("deprecation") org.bukkit.OfflinePlayer receiver = @@ -878,6 +1169,20 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // ── Gift-System: Ware geht an Empfänger B, nicht an Käufer A ────── + // Direkt nach Geldabzug prüfen – BEVOR irgendwelche Items/Commands verteilt werden + if (!isGiftDelivery && data.giftRecipient != null && !data.giftRecipient.isEmpty()) { + player.sendMessage(ChatColor.LIGHT_PURPLE + "🎁 Dein Geschenk wird an " + + ChatColor.WHITE + data.giftRecipient + + ChatColor.LIGHT_PURPLE + " gesendet!"); + player.sendMessage(ChatColor.GRAY + "Der Empfänger muss es ingame annehmen."); + handleIncomingGift(player.getName(), data.giftRecipient, + orderId, data.itemTitle, data.price, data.jsonPayload); + orderCache.remove(orderId); + return; + } + // ──────────────────────────────────────────────────────────────── + JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class); JsonObject rootObj = root.isJsonObject() ? root.getAsJsonObject() : null; @@ -3293,6 +3598,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { wpUrlCancel = wpBase + "/cancel_order"; wpUrlSellItems = wpBase + "/sell_items"; wpUrlSell = wpBase + "/sell_item"; + wpUrlGiftAccept = wpBase + "/gift_accept"; + wpUrlGiftDecline = wpBase + "/gift_decline"; targetServer = getConfig().getString("server-name", "survival").toLowerCase(); currency = getConfig().getString("currency-name", "Coins"); @@ -4231,23 +4538,49 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // =========================================================== + // GIFT REQUEST DATA CLASS + // =========================================================== + + private static class GiftRequest { + final int orderId; + final String senderName, recipientName, itemTitle, jsonPayload; + final double price; + + GiftRequest(int orderId, String senderName, String recipientName, + String itemTitle, double price, String jsonPayload) { + this.orderId = orderId; + this.senderName = senderName; + this.recipientName = recipientName; + this.itemTitle = itemTitle; + this.price = price; + this.jsonPayload = jsonPayload; + } + } + // =========================================================== // DATA CLASS // =========================================================== private static class OrderData { final int id, quantity; - final String itemId, itemTitle, jsonPayload; + final String itemId, itemTitle, jsonPayload, giftRecipient; final double price; OrderData(int id, String itemId, String itemTitle, double price, int quantity, String jsonPayload) { - this.id = id; - this.itemId = itemId; - this.itemTitle = itemTitle; - this.price = price; - this.quantity = quantity; - this.jsonPayload = jsonPayload; + this(id, itemId, itemTitle, price, quantity, jsonPayload, ""); + } + + OrderData(int id, String itemId, String itemTitle, + double price, int quantity, String jsonPayload, String giftRecipient) { + this.id = id; + this.itemId = itemId; + this.itemTitle = itemTitle; + this.price = price; + this.quantity = quantity; + this.jsonPayload = jsonPayload; + this.giftRecipient = giftRecipient != null ? giftRecipient : ""; } } } \ No newline at end of file