Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-25 21:53:13 +02:00
parent da3dfd717a
commit 738f6d0218
3 changed files with 555 additions and 2 deletions

View File

@@ -66,6 +66,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
private String wpUrlExecute;
private String wpUrlComplete;
private String wpUrlCancel;
private String wpUrlSellItems;
private String wpUrlSell;
private Gson gson = new Gson();
private boolean debug = false;
@@ -77,6 +79,14 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
private boolean flyRedeemDisabled = false;
private String incomeReceiver = "";
// Sell-Feature
private double sellPriceOffset = 0.0; // z.B. -10.0 = -10 % vom WordPress-Ankaufspreis
private boolean sellEnabled = true;
private final Map<UUID, List<SellManager.SellEntry>> sellCache = new HashMap<>();
private final Map<UUID, Integer> sellAmountPage = new HashMap<>();
private static final String GUI_SELL_TITLE = ChatColor.GOLD + "💰 Items verkaufen";
private Map<Integer, OrderData> orderCache = new HashMap<>();
private Map<UUID, Integer> activeOrderIds = new ConcurrentHashMap<>();
@@ -114,6 +124,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
wpUrlExecute = wpBase + "/execute_order";
wpUrlComplete = wpBase + "/complete_order";
wpUrlCancel = wpBase + "/cancel_order";
wpUrlSellItems = wpBase + "/sell_items";
wpUrlSell = wpBase + "/sell_item";
targetServer = getConfig().getString("server-name", "survival").toLowerCase();
currency = getConfig().getString("currency-name", "Coins");
@@ -121,6 +133,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
debug = getConfig().getBoolean("debug-mode", false);
flyRedeemDisabled = getConfig().getBoolean("fly-redeem-disabled", false);
incomeReceiver = getConfig().getString("income-receiver", "");
sellEnabled = getConfig().getBoolean("sell.enabled", true);
sellPriceOffset = getConfig().getDouble("sell.price-offset", 0.0);
if (apiKey.isEmpty()) {
getLogger().warning("⚠️ Kein api-key in config.yml gesetzt!");
@@ -148,9 +162,12 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
getCommand("flygive").setExecutor(new FlyGiveCommand());
getCommand("flypause").setExecutor(new FlyPauseCommand());
getCommand("rankinfo").setExecutor(new RankInfoCommand());
getCommand("sell").setExecutor(new SellCommand());
getCommand("wpis").setExecutor(new ReloadCommand());
startPolling(pollInterval);
flyManager.startSessionPersist();
startSellItemPolling();
getLogger().info("IngameShopSpigot v6.4 aktiv.");
}
@@ -175,6 +192,45 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
// POLLING
// ===========================================================
/** Lädt die Ankaufliste initial und alle 5 Minuten neu aus WordPress */
private void startSellItemPolling() {
Runnable refresh = () -> new BukkitRunnable() {
@Override public void run() {
try {
HttpURLConnection conn = openAuthConnection(
wpUrlSellItems + "?server=" + targetServer, "GET");
if (conn.getResponseCode() == 200) {
String body = readResponse(conn);
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
JsonArray items = json.getAsJsonArray("items");
List<SellManager.SellEntry> list = new ArrayList<>();
if (items != null) {
for (JsonElement el : items) {
JsonObject o = el.getAsJsonObject();
list.add(new SellManager.SellEntry(
o.get("item_id").getAsString(),
o.get("name").getAsString(),
o.get("buy_price").getAsDouble(),
o.get("sell_price").getAsDouble()
));
}
}
SellManager.getInstance().load(list);
if (debug) getLogger().info("[Sell] " + list.size() + " Items geladen.");
}
} catch (Exception e) {
if (debug) getLogger().log(Level.WARNING, "[Sell] Ladefehler", e);
}
}
}.runTaskAsynchronously(this);
refresh.run();
// Alle 5 Minuten neu laden
new BukkitRunnable() {
@Override public void run() { refresh.run(); }
}.runTaskTimer(this, 20L * 300, 20L * 300);
}
private void startPolling(long intervalTicks) {
this.task = new BukkitRunnable() {
@Override public void run() {
@@ -459,6 +515,9 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
public void onInventoryClick(InventoryClickEvent event) {
String title = event.getView().getTitle();
// ── Sell GUI ──────────────────────────────────────────────────────
if (handleSellClick(event)) return;
// ── Fly-Codes GUI ──────────────────────────────────────────────────
if (title.equals(GUI_FLYCODES)) {
event.setCancelled(true);
@@ -2061,6 +2120,470 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
// ===========================================================
// RELOAD COMMAND
// ===========================================================
private class ReloadCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
// /wpis reload
if (args.length == 0 || !args[0].equalsIgnoreCase("reload")) {
sender.sendMessage(ChatColor.YELLOW + "» IngameShop Pro");
sender.sendMessage(ChatColor.GRAY + " /wpis reload " + ChatColor.WHITE + " Config & Sell-Items neu laden");
return true;
}
if (!sender.hasPermission("ingameshop.reload")) {
sender.sendMessage(ChatColor.RED + "✗ Keine Berechtigung.");
return true;
}
sender.sendMessage(ChatColor.YELLOW + "⟳ IngameShop wird neu geladen...");
// Config neu laden
reloadConfig();
String domain = getConfig().getString("wordpress-url", "http://localhost");
if (domain.endsWith("/")) domain = domain.substring(0, domain.length() - 1);
wpBase = domain + "/wp-json/wis/v1";
wpUrlPending = wpBase + "/pending_orders";
wpUrlPendingOffline = wpBase + "/pending_offline";
wpUrlExecute = wpBase + "/execute_order";
wpUrlComplete = wpBase + "/complete_order";
wpUrlCancel = wpBase + "/cancel_order";
wpUrlSellItems = wpBase + "/sell_items";
wpUrlSell = wpBase + "/sell_item";
targetServer = getConfig().getString("server-name", "survival").toLowerCase();
currency = getConfig().getString("currency-name", "Coins");
apiKey = getConfig().getString("api-key", "");
debug = getConfig().getBoolean("debug-mode", false);
flyRedeemDisabled = getConfig().getBoolean("fly-redeem-disabled", false);
incomeReceiver = getConfig().getString("income-receiver", "");
sellEnabled = getConfig().getBoolean("sell.enabled", true);
sellPriceOffset = getConfig().getDouble("sell.price-offset", 0.0);
// Sell-Cache leeren und Items neu laden
sellCache.clear();
sellAmountPage.clear();
startSellItemPolling();
sender.sendMessage(ChatColor.GREEN + "✔ Config neu geladen!"
+ ChatColor.GRAY + " (Server: " + targetServer
+ " | Sell: " + (sellEnabled ? "aktiv" : "deaktiviert")
+ " | Offset: " + sellPriceOffset + " %)");
getLogger().info("[IngameShop] Config-Reload durch: " + sender.getName());
return true;
}
}
// ===========================================================
// SELL COMMAND
// ===========================================================
/** /sell [hand|all] */
private class SellCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; }
if (!sellEnabled) {
sender.sendMessage(ChatColor.RED + "✗ Der Ankauf ist auf diesem Server deaktiviert.");
return true;
}
Player p = (Player) sender;
// /sell hand → sofort Item in der Hand verkaufen (1 Stack)
if (args.length > 0 && args[0].equalsIgnoreCase("hand")) {
sellHandItem(p);
return true;
}
// /sell all → alle verkaufbaren Items aus dem Inventar verkaufen
if (args.length > 0 && args[0].equalsIgnoreCase("all")) {
sellAllItems(p);
return true;
}
// /sell → GUI öffnen
openSellGUI(p, 0);
return true;
}
}
// ===========================================================
// SELL GUI
// ===========================================================
private void openSellGUI(Player player, int page) {
new BukkitRunnable() {
@Override public void run() {
List<SellManager.SellEntry> entries = SellManager.getInstance().getEntries();
Bukkit.getScheduler().runTask(IngameShopSpigot.this,
() -> renderSellGUI(player, entries, page));
}
}.runTaskAsynchronously(this);
}
private void renderSellGUI(Player player, List<SellManager.SellEntry> entries, int page) {
final int PAGE_SIZE = 45;
int totalPages = Math.max(1, (int) Math.ceil(entries.size() / (double) PAGE_SIZE));
page = Math.max(0, Math.min(page, totalPages - 1));
sellAmountPage.put(player.getUniqueId(), page);
sellCache.put(player.getUniqueId(), entries);
Inventory gui = Bukkit.createInventory(null, 54, GUI_SELL_TITLE);
// Pane-Reihe unten
ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta pM = pane.getItemMeta(); pM.setDisplayName(" "); pane.setItemMeta(pM);
for (int i = 45; i < 54; i++) gui.setItem(i, pane);
int start = page * PAGE_SIZE;
int end = Math.min(start + PAGE_SIZE, entries.size());
for (int i = start; i < end; i++) {
SellManager.SellEntry e = entries.get(i);
ItemStack display = parseItem(e.itemId);
if (display == null) display = new ItemStack(Material.BARRIER);
// Wie viel hat der Spieler davon?
int playerHas = countItemInInventory(player, e.itemId);
double totalValue = playerHas * applyOffset(e.sellPrice);
ItemMeta meta = display.getItemMeta();
meta.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD + e.name);
List<String> lore = new ArrayList<>();
lore.add(ChatColor.GRAY + "──────────────────────");
lore.add(ChatColor.WHITE + "Ankauf: " + ChatColor.GREEN
+ String.format("%.2f", applyOffset(e.sellPrice)) + " " + currency + " /Stück");
lore.add(ChatColor.WHITE + "Kaufpreis: " + ChatColor.GRAY
+ e.buyPrice + " " + currency);
lore.add(ChatColor.GRAY + "──────────────────────");
if (playerHas > 0) {
lore.add(ChatColor.AQUA + "Du hast: " + ChatColor.WHITE + playerHas + "x");
lore.add(ChatColor.GREEN + "Wert: " + ChatColor.WHITE
+ String.format("%.2f", totalValue) + " " + currency);
lore.add(ChatColor.GRAY + "──────────────────────");
lore.add(ChatColor.YELLOW + "▶ Linksklick: " + ChatColor.WHITE + "Alles verkaufen");
lore.add(ChatColor.YELLOW + "▶ Rechtsklick: " + ChatColor.WHITE + "1 Stack verkaufen");
} else {
lore.add(ChatColor.RED + "✗ Nicht in deinem Inventar");
}
meta.setLore(lore);
display.setItemMeta(meta);
gui.setItem(i - start, display);
}
// Nav
if (page > 0) {
ItemStack prev = new ItemStack(Material.ARROW);
ItemMeta pm = prev.getItemMeta();
pm.setDisplayName(ChatColor.YELLOW + "◀ Vorherige Seite");
prev.setItemMeta(pm);
gui.setItem(45, prev);
}
if (page < totalPages - 1) {
ItemStack next = new ItemStack(Material.ARROW);
ItemMeta nm = next.getItemMeta();
nm.setDisplayName(ChatColor.YELLOW + "Nächste Seite ▶");
next.setItemMeta(nm);
gui.setItem(53, next);
}
// Info
ItemStack info = new ItemStack(Material.GOLD_INGOT);
ItemMeta im = info.getItemMeta();
im.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + "Item-Ankauf");
im.setLore(Arrays.asList(
ChatColor.GRAY + "Seite " + (page + 1) + " / " + totalPages,
ChatColor.GRAY + "" + entries.size() + " ankaufbare Items",
ChatColor.GRAY + "──────────────────────",
ChatColor.WHITE + "/sell all " + ChatColor.GRAY + "→ alles auf einmal verkaufen",
ChatColor.WHITE + "/sell hand " + ChatColor.GRAY + "→ Item in der Hand verkaufen"
));
info.setItemMeta(im);
gui.setItem(49, info);
if (entries.isEmpty()) {
ItemStack empty = new ItemStack(Material.BARRIER);
ItemMeta em = empty.getItemMeta();
em.setDisplayName(ChatColor.RED + "Keine ankaufbaren Items konfiguriert");
empty.setItemMeta(em);
gui.setItem(22, empty);
}
player.openInventory(gui);
}
// ===========================================================
// SELL INVENTORY CLICK
// ===========================================================
// Wird in onInventoryClick() aufgerufen dort einhaken
private boolean handleSellClick(InventoryClickEvent event) {
String title = event.getView().getTitle();
if (!GUI_SELL_TITLE.equals(title)) return false;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player)) return true;
Player p = (Player) event.getWhoClicked();
int slot = event.getRawSlot();
List<SellManager.SellEntry> entries = sellCache.get(p.getUniqueId());
if (entries == null) return true;
int page = sellAmountPage.getOrDefault(p.getUniqueId(), 0);
int total = (int) Math.ceil(entries.size() / 45.0);
// Navigation
if (slot == 45 && page > 0) { renderSellGUI(p, entries, page - 1); return true; }
if (slot == 53 && page < total - 1) { renderSellGUI(p, entries, page + 1); return true; }
if (slot < 0 || slot > 44) return true;
int idx = page * 45 + slot;
if (idx >= entries.size()) return true;
SellManager.SellEntry entry = entries.get(idx);
boolean rightClick = event.getClick().isRightClick();
// Stack (64) oder alles verkaufen
int sellAmount = rightClick
? Math.min(64, countItemInInventory(p, entry.itemId))
: countItemInInventory(p, entry.itemId);
if (sellAmount <= 0) {
p.sendMessage(ChatColor.RED + "✗ Du hast kein " + entry.name + " im Inventar.");
return true;
}
processSell(p, entry, sellAmount, () -> {
// GUI neu rendern nach Verkauf
List<SellManager.SellEntry> fresh = sellCache.get(p.getUniqueId());
if (fresh != null) renderSellGUI(p, fresh, sellAmountPage.getOrDefault(p.getUniqueId(), 0));
});
return true;
}
// ===========================================================
// SELL LOGIC
// ===========================================================
private void sellHandItem(Player player) {
ItemStack hand = player.getInventory().getItemInMainHand();
if (hand == null || hand.getType() == Material.AIR) {
player.sendMessage(ChatColor.RED + "✗ Halte ein Item in der Hand.");
return;
}
String materialName = hand.getType().name().toLowerCase();
SellManager.SellEntry entry = SellManager.getInstance().findByMaterial(materialName);
if (entry == null) {
player.sendMessage(ChatColor.RED + "✗ Dieses Item wird nicht angekauft.");
return;
}
int amount = hand.getAmount();
processSell(player, entry, amount, null);
}
private void sellAllItems(Player player) {
List<SellManager.SellEntry> entries = SellManager.getInstance().getEntries();
if (entries.isEmpty()) {
player.sendMessage(ChatColor.RED + "✗ Keine ankaufbaren Items konfiguriert.");
return;
}
double totalEarned = 0;
int totalItems = 0;
for (SellManager.SellEntry entry : entries) {
int count = countItemInInventory(player, entry.itemId);
if (count <= 0) continue;
double price = applyOffset(entry.sellPrice);
double earned = price * count;
removeItemsFromInventory(player, entry.itemId, count);
totalEarned += earned;
totalItems += count;
}
if (totalItems == 0) {
player.sendMessage(ChatColor.YELLOW + "✗ Keine ankaufbaren Items im Inventar.");
return;
}
final double finalEarned = totalEarned;
final int finalItems = totalItems;
new BukkitRunnable() {
@Override public void run() {
// Batch: kein individueller WP-Call, direkt Vault
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
econ.depositPlayer(player, finalEarned);
player.sendMessage(ChatColor.GREEN + "💰 /sell all: "
+ ChatColor.WHITE + finalItems + " Items"
+ ChatColor.GREEN + " verkauft für "
+ ChatColor.YELLOW + String.format("%.2f", finalEarned)
+ " " + currency + ChatColor.GREEN + "!");
player.playSound(player.getLocation(),
Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8F, 1.0F);
});
}
}.runTaskAsynchronously(this);
}
private void processSell(Player player, SellManager.SellEntry entry,
int amount, Runnable afterCallback) {
double pricePerItem = applyOffset(entry.sellPrice);
double total = pricePerItem * amount;
// Items aus dem Inventar nehmen (sync)
int removed = removeItemsFromInventory(player, entry.itemId, amount);
if (removed == 0) {
player.sendMessage(ChatColor.RED + "✗ Keine " + entry.name + " im Inventar.");
return;
}
double actualTotal = pricePerItem * removed;
new BukkitRunnable() {
@Override public void run() {
try {
HttpURLConnection conn = openAuthConnection(wpUrlSell, "POST");
String body = "{\"player\":\"" + player.getName()
+ "\",\"server\":\"" + targetServer
+ "\",\"item_id\":\"" + entry.itemId
+ "\",\"quantity\":" + removed + "}";
writeJson(conn, body);
int code = conn.getResponseCode();
if (code == 200) {
String resp = readResponse(conn);
JsonObject json = JsonParser.parseString(resp).getAsJsonObject();
double wpTotal = json.has("total") ? json.get("total").getAsDouble() : actualTotal;
// Offset nochmals anwenden falls config abweicht
double finalPay = wpTotal + (wpTotal * sellPriceOffset / 100.0);
finalPay = Math.max(0, finalPay);
final double pay = finalPay;
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
econ.depositPlayer(player, pay);
player.sendMessage(ChatColor.GREEN + "💰 "
+ ChatColor.WHITE + removed + "x " + entry.name
+ ChatColor.GREEN + " verkauft → "
+ ChatColor.YELLOW + String.format("%.2f", pay)
+ " " + currency + ChatColor.GREEN + "!");
player.playSound(player.getLocation(),
Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8F, 1.1F);
if (afterCallback != null) afterCallback.run();
});
} else {
// WP nicht erreichbar → Fallback: direkt auszahlen
final double fallback = actualTotal;
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
econ.depositPlayer(player, fallback);
player.sendMessage(ChatColor.GREEN + "💰 "
+ ChatColor.WHITE + removed + "x " + entry.name
+ ChatColor.GREEN + " verkauft → "
+ ChatColor.YELLOW + String.format("%.2f", fallback)
+ " " + currency + ChatColor.GREEN + "!");
if (debug) getLogger().warning("WP sell_item HTTP " + code + " Fallback genutzt");
if (afterCallback != null) afterCallback.run();
});
}
} catch (Exception ex) {
final double fallback = actualTotal;
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
econ.depositPlayer(player, fallback);
player.sendMessage(ChatColor.GREEN + "💰 "
+ removed + "x " + entry.name + ""
+ String.format("%.2f", fallback) + " " + currency);
if (debug) getLogger().log(Level.WARNING, "sell_item Fehler", ex);
if (afterCallback != null) afterCallback.run();
});
}
}
}.runTaskAsynchronously(this);
}
/** Wendet den config sell.price-offset an (z.B. -10.0 = -10 %) */
private double applyOffset(double basePrice) {
if (sellPriceOffset == 0.0) return basePrice;
return Math.max(0, basePrice + basePrice * sellPriceOffset / 100.0);
}
/** Zählt wie viele Items des Typs im Inventar sind */
private int countItemInInventory(Player player, String itemId) {
ItemStack template = parseItem(itemId);
if (template == null) return 0;
Material mat = template.getType();
int count = 0;
for (ItemStack stack : player.getInventory().getContents()) {
if (stack != null && stack.getType() == mat) count += stack.getAmount();
}
return count;
}
/** Entfernt bis zu `amount` Items aus dem Inventar, gibt tatsächlich entfernten Betrag zurück */
private int removeItemsFromInventory(Player player, String itemId, int amount) {
ItemStack template = parseItem(itemId);
if (template == null) return 0;
Material mat = template.getType();
int removed = 0;
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
if (removed >= amount) break;
ItemStack stack = contents[i];
if (stack == null || stack.getType() != mat) continue;
int take = Math.min(stack.getAmount(), amount - removed);
removed += take;
if (take == stack.getAmount()) contents[i] = null;
else stack.setAmount(stack.getAmount() - take);
}
player.getInventory().setContents(contents);
return removed;
}
// ===========================================================
// SELL MANAGER
// ===========================================================
private static class SellManager {
private static SellManager instance;
private final List<SellEntry> entries = new ArrayList<>();
private SellManager() {}
static SellManager getInstance() {
if (instance == null) instance = new SellManager();
return instance;
}
synchronized void load(List<SellEntry> fresh) {
entries.clear();
entries.addAll(fresh);
}
synchronized List<SellEntry> getEntries() {
return new ArrayList<>(entries);
}
synchronized SellEntry findByMaterial(String materialName) {
String clean = materialName.toLowerCase().replace("minecraft:", "");
for (SellEntry e : entries) {
String eid = e.itemId.toLowerCase().replace("minecraft:", "");
if (eid.equals(clean)) return e;
}
return null;
}
static class SellEntry {
final String itemId, name;
final double buyPrice, sellPrice;
SellEntry(String itemId, String name, double buyPrice, double sellPrice) {
this.itemId = itemId;
this.name = name;
this.buyPrice = buyPrice;
this.sellPrice = sellPrice;
}
}
}
// ===========================================================
// DATA CLASS
// ===========================================================

View File

@@ -35,4 +35,20 @@ mysql:
port: "3306"
database: "minecraft"
username: "root"
password: "DEIN_PASSWORT"
password: "DEIN_PASSWORT"
# ──────────────────────────────────────────────
# Ankauf-Einstellungen (Spieler verkaufen Items)
# ──────────────────────────────────────────────
sell:
# Ankauf auf diesem Server aktivieren
enabled: true
# Preiskorrektur relativ zum WP-Ankaufspreis (in Prozent)
# Beispiele:
# 0.0 → exakt den WP-Ankaufspreis zahlen
# -10.0 → 10 % weniger als der WP-Ankaufspreis
# +5.0 → 5 % mehr als der WP-Ankaufspreis
# Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin
# unter Items → <Item bearbeiten> → "Ankauf aktivieren"
price-offset: -10.0

View File

@@ -35,6 +35,14 @@ commands:
description: Zeigt deine aktiven (zeitbasierten) Ränge an
usage: /rankinfo
permission: ingameshop.rankinfo
sell:
description: Verkauft Items an den Shop (GUI, /sell hand, /sell all)
usage: /sell [hand|all]
permission: ingameshop.sell
wpis:
description: IngameShop Admin-Befehle
usage: /wpis <reload>
permission: ingameshop.reload
permissions:
ingameshop.orders:
@@ -57,4 +65,10 @@ permissions:
default: true
ingameshop.rankinfo:
description: Kann eigene aktive Ränge einsehen
default: true
default: true
ingameshop.sell:
description: Kann Items an den Shop verkaufen
default: true
ingameshop.reload:
description: Kann die IngameShop-Config live neu laden
default: op