Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-30 09:34:22 +02:00
parent d6ed6544db
commit 902f8ed43c

View File

@@ -92,6 +92,12 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
private Map<Integer, OrderData> orderCache = new HashMap<>();
private Map<UUID, Integer> activeOrderIds = new ConcurrentHashMap<>();
// Gift-System: Ausstehende Geschenk-Anfragen (Empfänger-UUID → OrderData)
private final Map<UUID, GiftRequest> 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<String> 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 : "";
}
}
}