Update from Git Manager GUI
This commit is contained in:
@@ -4,10 +4,14 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
@@ -24,6 +28,7 @@ import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -38,21 +43,29 @@ import com.google.gson.JsonParser;
|
||||
public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
|
||||
private static Economy econ = null;
|
||||
private String wpBase;
|
||||
private String wpBase;
|
||||
private String wpUrlPending;
|
||||
private String wpUrlPendingOffline; // NEU: Offline-Queue
|
||||
private String wpUrlExecute;
|
||||
private String wpUrlComplete;
|
||||
private String wpUrlCancel; // NEU
|
||||
|
||||
private String wpUrlCancel;
|
||||
|
||||
private Gson gson = new Gson();
|
||||
private boolean debug = false;
|
||||
private BukkitTask task;
|
||||
|
||||
private String currency = "Coins";
|
||||
private String currency = "Coins";
|
||||
private String targetServer = "survival";
|
||||
|
||||
private Map<Integer, OrderData> orderCache = new HashMap<>();
|
||||
private Map<UUID, Integer> activeOrderIds = new HashMap<>();
|
||||
// NEU: API-Key für gesicherte Endpunkte
|
||||
private String apiKey = "";
|
||||
|
||||
private Map<Integer, OrderData> orderCache = new HashMap<>();
|
||||
private Map<UUID, Integer> activeOrderIds = new HashMap<>();
|
||||
|
||||
// ===========================================================
|
||||
// LIFECYCLE
|
||||
// ===========================================================
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -64,32 +77,48 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
|
||||
saveDefaultConfig();
|
||||
reloadConfig();
|
||||
|
||||
String domain = getConfig().getString("wordpress-url", "http://localhost/Windelgeschichten.org");
|
||||
if (domain.endsWith("/")) {
|
||||
domain = domain.substring(0, domain.length() - 1);
|
||||
}
|
||||
|
||||
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";
|
||||
wpUrlExecute = wpBase + "/execute_order";
|
||||
wpUrlComplete = wpBase + "/complete_order";
|
||||
wpUrlCancel = wpBase + "/cancel_order"; // NEU
|
||||
|
||||
this.targetServer = getConfig().getString("server-name", "survival").toLowerCase();
|
||||
this.currency = getConfig().getString("currency-name", "Coins");
|
||||
|
||||
|
||||
wpUrlPending = wpBase + "/pending_orders";
|
||||
wpUrlPendingOffline = wpBase + "/pending_offline"; // NEU
|
||||
wpUrlExecute = wpBase + "/execute_order";
|
||||
wpUrlComplete = wpBase + "/complete_order";
|
||||
wpUrlCancel = wpBase + "/cancel_order";
|
||||
|
||||
targetServer = getConfig().getString("server-name", "survival").toLowerCase();
|
||||
currency = getConfig().getString("currency-name", "Coins");
|
||||
apiKey = getConfig().getString("api-key", ""); // NEU
|
||||
debug = getConfig().getBoolean("debug-mode", false);
|
||||
|
||||
if (apiKey.isEmpty()) {
|
||||
getLogger().warning("⚠️ Kein api-key in config.yml gesetzt! Geschützte Endpunkte sind nicht erreichbar.");
|
||||
getLogger().warning(" Trage den Key aus den WordPress-Einstellungen (Ingame Shop → Einstellungen → 🔑 API-Key) ein.");
|
||||
}
|
||||
|
||||
int intervalSeconds = getConfig().getInt("check-interval", 10);
|
||||
long pollInterval = intervalSeconds * 20L;
|
||||
debug = getConfig().getBoolean("debug-mode", false);
|
||||
|
||||
long pollInterval = intervalSeconds * 20L;
|
||||
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
|
||||
// /orders Befehl registrieren
|
||||
getCommand("orders").setExecutor(new OrdersCommand());
|
||||
|
||||
startPolling(pollInterval);
|
||||
|
||||
getLogger().info("=== IngameShopSpigot v6.2 (Cancel Logic) CONFIG ===");
|
||||
getLogger().info("Domain: " + domain);
|
||||
getLogger().info("Target Server: " + this.targetServer);
|
||||
getLogger().info("Currency: " + this.currency);
|
||||
|
||||
getLogger().info("=== IngameShopSpigot v6.3 (API-Key + Offline-Queue) ===");
|
||||
getLogger().info("Domain: " + domain);
|
||||
getLogger().info("Target Server: " + targetServer);
|
||||
getLogger().info("Currency: " + currency);
|
||||
getLogger().info("API-Key: " + (apiKey.isEmpty() ? "❌ NICHT GESETZT" : "✅ gesetzt"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (task != null) task.cancel();
|
||||
getLogger().info("IngameShopSpigot gestoppt");
|
||||
}
|
||||
|
||||
private boolean setupEconomy() {
|
||||
@@ -99,6 +128,10 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
return econ != null;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// POLLING
|
||||
// ===========================================================
|
||||
|
||||
private void startPolling(long intervalTicks) {
|
||||
this.task = new BukkitRunnable() {
|
||||
@Override
|
||||
@@ -107,61 +140,13 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
try {
|
||||
String urlString = wpUrlPending + "?player=" + p.getName();
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
|
||||
if (conn.getResponseCode() == 200) {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)
|
||||
);
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
JsonArray orders = json.getAsJsonArray("orders");
|
||||
|
||||
if (orders != null && orders.size() > 0) {
|
||||
for (int i = 0; i < orders.size(); i++) {
|
||||
JsonObject order = orders.get(i).getAsJsonObject();
|
||||
|
||||
int id = order.get("id").getAsInt();
|
||||
String orderServer = order.has("server") ? order.get("server").getAsString().toLowerCase() : "";
|
||||
|
||||
if (!orderServer.equals(targetServer)) {
|
||||
if (debug) getLogger().info("Order #" + id + " ist für Server '" + orderServer + "'. Ignoriere.");
|
||||
continue;
|
||||
}
|
||||
|
||||
String jsonResponse = order.has("response") ? order.get("response").getAsString() : "[]";
|
||||
String itemTitle = order.get("item_title").getAsString();
|
||||
double price = order.get("price").getAsDouble();
|
||||
String status = order.get("status").getAsString();
|
||||
|
||||
if ("pending".equals(status)) {
|
||||
OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse);
|
||||
orderCache.put(id, data);
|
||||
activeOrderIds.put(p.getUniqueId(), id);
|
||||
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
openConfirmGUI(p, id, itemTitle, price);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (debug) Bukkit.getLogger().log(Level.WARNING, "Polling Fehler", e);
|
||||
// RACE CONDITION FIX: Spieler der bereits eine aktive Order im GUI hat
|
||||
// bekommt keine neue Order angezeigt bis die vorherige abgeschlossen ist
|
||||
if (activeOrderIds.containsKey(p.getUniqueId())) {
|
||||
if (debug) getLogger().info("Spieler " + p.getName() + " hat bereits aktive Order – überspringe Poll.");
|
||||
continue;
|
||||
}
|
||||
fetchPendingOrders(p, wpUrlPending);
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(IngameShopSpigot.this);
|
||||
@@ -169,25 +154,92 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
}.runTaskTimer(this, 20L, intervalTicks);
|
||||
}
|
||||
|
||||
private void openConfirmGUI(Player player, int orderId, String itemTitle, double price) {
|
||||
Inventory gui = Bukkit.createInventory(null, 27, "§eKauf bestätigen?");
|
||||
private void fetchPendingOrders(Player p, String endpointUrl) {
|
||||
try {
|
||||
String urlString = endpointUrl + "?player=" + p.getName();
|
||||
HttpURLConnection conn = openAuthConnection(urlString, "GET");
|
||||
|
||||
ItemStack info = new ItemStack(Material.WRITTEN_BOOK);
|
||||
if (conn.getResponseCode() == 200) {
|
||||
String body = readResponse(conn);
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
JsonArray orders = json.getAsJsonArray("orders");
|
||||
|
||||
if (orders != null && orders.size() > 0) {
|
||||
JsonObject order = orders.get(0).getAsJsonObject(); // immer nur erste Order zeigen
|
||||
|
||||
int id = order.get("id").getAsInt();
|
||||
String orderServer = order.has("server") ? order.get("server").getAsString().toLowerCase() : "";
|
||||
|
||||
if (!orderServer.equals(targetServer)) {
|
||||
if (debug) getLogger().info("Order #" + id + " ist für Server '" + orderServer + "'. Ignoriere.");
|
||||
return;
|
||||
}
|
||||
|
||||
String jsonResponse = order.has("response") ? order.get("response").getAsString() : "[]";
|
||||
String itemTitle = order.get("item_title").getAsString();
|
||||
double price = order.get("price").getAsDouble();
|
||||
|
||||
OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse);
|
||||
orderCache.put(id, data);
|
||||
activeOrderIds.put(p.getUniqueId(), id);
|
||||
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> openConfirmGUI(p, id, itemTitle, price, jsonResponse));
|
||||
}
|
||||
} else if (conn.getResponseCode() == 401) {
|
||||
getLogger().warning("❌ API-Key ungültig oder nicht gesetzt! HTTP 401 von " + endpointUrl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (debug) getLogger().log(Level.WARNING, "Polling Fehler für " + p.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// OFFLINE-QUEUE: beim Login ausstehende Orders liefern
|
||||
// ===========================================================
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player p = event.getPlayer();
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchPendingOrders(p, wpUrlPendingOffline);
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// GUI: zeigt echtes Item-Icon aus dem Warenkorb-Payload
|
||||
// ===========================================================
|
||||
|
||||
private void openConfirmGUI(Player player, int orderId, String itemTitle, double price, String jsonPayload) {
|
||||
Inventory gui = Bukkit.createInventory(null, 27, ChatColor.YELLOW + "Kauf bestätigen?");
|
||||
|
||||
// NEU: erstes Item aus dem Payload als Icon verwenden
|
||||
Material iconMaterial = getFirstItemMaterial(jsonPayload);
|
||||
ItemStack info = new ItemStack(iconMaterial);
|
||||
ItemMeta infoMeta = info.getItemMeta();
|
||||
infoMeta.setDisplayName(ChatColor.GOLD + itemTitle);
|
||||
infoMeta.setLore(java.util.Arrays.asList(
|
||||
ChatColor.WHITE + "Preis: " + price + " " + currency
|
||||
infoMeta.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + itemTitle);
|
||||
infoMeta.setLore(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"
|
||||
));
|
||||
info.setItemMeta(infoMeta);
|
||||
|
||||
ItemStack yes = new ItemStack(Material.LIME_WOOL);
|
||||
ItemMeta yesMeta = yes.getItemMeta();
|
||||
yesMeta.setDisplayName(ChatColor.GREEN + "§lJA, kaufen!");
|
||||
yesMeta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "✔ JA, kaufen!");
|
||||
yesMeta.setLore(Arrays.asList(ChatColor.GRAY + "" + price + " " + currency + " werden abgezogen"));
|
||||
yes.setItemMeta(yesMeta);
|
||||
|
||||
|
||||
ItemStack no = new ItemStack(Material.RED_WOOL);
|
||||
ItemMeta noMeta = no.getItemMeta();
|
||||
noMeta.setDisplayName(ChatColor.RED + "§lNEIN, abbrechen");
|
||||
noMeta.setDisplayName(ChatColor.RED + "" + ChatColor.BOLD + "✘ NEIN, abbrechen");
|
||||
noMeta.setLore(Arrays.asList(ChatColor.GRAY + "Bestellung wird storniert"));
|
||||
no.setItemMeta(noMeta);
|
||||
|
||||
ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
|
||||
@@ -203,15 +255,43 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
player.openInventory(gui);
|
||||
}
|
||||
|
||||
/**
|
||||
* NEU: Liest das erste Item aus dem JSON-Payload und gibt das passende Material zurück.
|
||||
* Fällt auf CHEST zurück wenn nichts parsebar ist.
|
||||
*/
|
||||
private Material getFirstItemMaterial(String jsonPayload) {
|
||||
try {
|
||||
JsonElement root = gson.fromJson(jsonPayload, JsonElement.class);
|
||||
JsonArray items;
|
||||
if (root.isJsonObject()) {
|
||||
items = root.getAsJsonObject().getAsJsonArray("items");
|
||||
} else {
|
||||
items = root.getAsJsonArray();
|
||||
}
|
||||
if (items != null && items.size() > 0) {
|
||||
String itemId = items.get(0).getAsJsonObject().get("id").getAsString();
|
||||
ItemStack test = parseItem(itemId);
|
||||
if (test != null) return test.getType();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (debug) getLogger().log(Level.WARNING, "getFirstItemMaterial Fehler", e);
|
||||
}
|
||||
return Material.CHEST; // sicherer Fallback
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// INVENTORY CLICK
|
||||
// ===========================================================
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!event.getView().getTitle().contains("Kauf bestätigen?")) return;
|
||||
event.setCancelled(true);
|
||||
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||
|
||||
Player p = (Player) event.getWhoClicked();
|
||||
int slot = event.getRawSlot();
|
||||
|
||||
|
||||
Player p = (Player) event.getWhoClicked();
|
||||
int slot = event.getRawSlot();
|
||||
|
||||
if (slot == 11) { // JA
|
||||
Integer orderId = activeOrderIds.get(p.getUniqueId());
|
||||
if (orderId != null) {
|
||||
@@ -225,13 +305,17 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
} else if (slot == 15) { // NEIN
|
||||
Integer orderId = activeOrderIds.get(p.getUniqueId());
|
||||
if (orderId != null) {
|
||||
cancelOrder(p, orderId); // NEU
|
||||
cancelOrder(p, orderId);
|
||||
p.closeInventory();
|
||||
activeOrderIds.remove(p.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// ORDER PROCESSING
|
||||
// ===========================================================
|
||||
|
||||
private void processOrder(Player player, int orderId) {
|
||||
OrderData data = orderCache.get(orderId);
|
||||
if (data == null) {
|
||||
@@ -243,76 +327,54 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
URL url = new URL(wpUrlExecute);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
HttpURLConnection conn = openAuthConnection(wpUrlExecute, "POST");
|
||||
writeJson(conn, "{\"id\":\"" + orderId + "\"}");
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
if (code == 200) {
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> executeShopLogic(player, data, orderId));
|
||||
} else if (code == 401) {
|
||||
getLogger().warning("❌ API-Key ungültig bei /execute_order");
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
player.sendMessage(ChatColor.RED + "❌ Server-Konfigurationsfehler (Auth)."));
|
||||
} else {
|
||||
if (debug) getLogger().warning("Execute Order API Code: " + responseCode);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
player.sendMessage(ChatColor.RED + "❌ Server-Fehler beim Starten des Kaufs.");
|
||||
});
|
||||
if (debug) getLogger().warning("Execute Order HTTP " + code);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
player.sendMessage(ChatColor.RED + "❌ Server-Fehler beim Starten des Kaufs."));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler bei /execute_order", e);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Kauf.");
|
||||
});
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Kauf."));
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// NEU: ABBRECHEN LOGIC
|
||||
// ===========================================================
|
||||
private void cancelOrder(Player player, int orderId) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
URL url = new URL(wpUrlCancel);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
|
||||
if (responseCode == 200) {
|
||||
orderCache.remove(orderId);
|
||||
HttpURLConnection conn = openAuthConnection(wpUrlCancel, "POST");
|
||||
writeJson(conn, "{\"id\":\"" + orderId + "\"}");
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
orderCache.remove(orderId);
|
||||
|
||||
if (code == 200) {
|
||||
player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen.");
|
||||
if (debug) getLogger().info("✅ Order #" + orderId + " successfully cancelled");
|
||||
if (debug) getLogger().info("✅ Order #" + orderId + " cancelled");
|
||||
} else {
|
||||
if (debug) getLogger().warning("⚠️ Cancel Order API returned code: " + responseCode);
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler beim Abbrechen des Kaufs.");
|
||||
// Fallback: Cache leeren damit es nicht als Loop erscheint
|
||||
orderCache.remove(orderId);
|
||||
if (debug) getLogger().warning("⚠️ Cancel HTTP " + code);
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler beim Abbrechen – Order wird lokal verworfen.");
|
||||
activeOrderIds.remove(player.getUniqueId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "Fehler bei /cancel_order", e);
|
||||
// Fallback: Cache leeren
|
||||
orderCache.remove(orderId);
|
||||
activeOrderIds.remove(player.getUniqueId());
|
||||
player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen (lokale Bestätigung).");
|
||||
player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen (lokal).");
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
@@ -321,70 +383,68 @@ 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 + "❌ Du hast nicht genug Geld! (Benötigt: " + data.price + " " + currency + ")");
|
||||
// Wenn der Kauf fehlschagt (Geld), sollte die Bestellung abgebrochen werden
|
||||
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.");
|
||||
|
||||
try {
|
||||
JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class);
|
||||
JsonArray items;
|
||||
|
||||
if (root.isJsonObject()) {
|
||||
items = root.getAsJsonObject().getAsJsonArray("items");
|
||||
} else {
|
||||
items = root.getAsJsonArray();
|
||||
}
|
||||
|
||||
int totalItemsGiven = 0;
|
||||
|
||||
for (JsonElement e : items) {
|
||||
JsonObject itemObj = e.getAsJsonObject();
|
||||
String itemId = itemObj.get("id").getAsString();
|
||||
int amount = itemObj.get("amount").getAsInt();
|
||||
|
||||
ItemStack item = parseItem(itemId);
|
||||
if (item != null) {
|
||||
int remaining = amount;
|
||||
while (remaining > 0) {
|
||||
int stackSize = Math.min(remaining, item.getMaxStackSize());
|
||||
ItemStack stack = item.clone();
|
||||
stack.setAmount(stackSize);
|
||||
|
||||
if (player.getInventory().firstEmpty() == -1) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), stack);
|
||||
} else {
|
||||
player.getInventory().addItem(stack);
|
||||
}
|
||||
remaining -= stackSize;
|
||||
}
|
||||
totalItemsGiven++;
|
||||
} else {
|
||||
player.sendMessage(ChatColor.RED + "❌ Item '" + itemId + "' konnte nicht gefunden werden.");
|
||||
}
|
||||
}
|
||||
|
||||
if (totalItemsGiven > 0) {
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
|
||||
player.sendMessage(ChatColor.GREEN + "✅ Kauf erfolgreich abgeschlossen!");
|
||||
}
|
||||
|
||||
markOrderCompleted(orderId);
|
||||
orderCache.remove(orderId);
|
||||
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler beim Verarbeiten der JSON-Items", e);
|
||||
player.sendMessage(ChatColor.RED + "❌ Fehler beim Verteilen der Items. Bitte Admin kontaktieren.");
|
||||
markOrderCompleted(orderId);
|
||||
|
||||
JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class);
|
||||
JsonArray items;
|
||||
if (root.isJsonObject()) {
|
||||
items = root.getAsJsonObject().getAsJsonArray("items");
|
||||
} else {
|
||||
items = root.getAsJsonArray();
|
||||
}
|
||||
|
||||
|
||||
int totalGiven = 0;
|
||||
for (JsonElement e : items) {
|
||||
JsonObject itemObj = e.getAsJsonObject();
|
||||
String itemId = itemObj.get("id").getAsString();
|
||||
int amount = itemObj.get("amount").getAsInt();
|
||||
|
||||
ItemStack item = parseItem(itemId);
|
||||
if (item != null) {
|
||||
giveItems(player, item, amount);
|
||||
totalGiven++;
|
||||
} else {
|
||||
player.sendMessage(ChatColor.RED + "❌ Item '" + itemId + "' nicht gefunden.");
|
||||
}
|
||||
}
|
||||
|
||||
if (totalGiven > 0) {
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
|
||||
player.sendMessage(ChatColor.GREEN + "✅ Kauf erfolgreich abgeschlossen!");
|
||||
}
|
||||
|
||||
markOrderCompleted(orderId);
|
||||
orderCache.remove(orderId);
|
||||
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "Fehler bei Shop-Logik", e);
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler.");
|
||||
player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Verteilen. Admin kontaktieren.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NEU: Gibt Items an den Spieler – volles Inventar → natürlicher Drop
|
||||
* (bestehende Logik unverändert, aber in eigene Methode ausgelagert)
|
||||
*/
|
||||
private void giveItems(Player player, ItemStack template, int amount) {
|
||||
int remaining = amount;
|
||||
while (remaining > 0) {
|
||||
int stackSize = Math.min(remaining, template.getMaxStackSize());
|
||||
ItemStack stack = template.clone();
|
||||
stack.setAmount(stackSize);
|
||||
if (player.getInventory().firstEmpty() == -1) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), stack);
|
||||
player.sendMessage(ChatColor.YELLOW + "⚠ Inventar voll – Items wurden gedroppt.");
|
||||
} else {
|
||||
player.getInventory().addItem(stack);
|
||||
}
|
||||
remaining -= stackSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,103 +453,195 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String jsonInputString = "{\"id\":\"" + orderId + "\"}";
|
||||
|
||||
if (debug) getLogger().info("🔄 Marking order #" + orderId + " as completed...");
|
||||
|
||||
URL url = new URL(wpUrlComplete);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try(OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
|
||||
if (debug) {
|
||||
if (responseCode == 200) {
|
||||
getLogger().info("✅ Order #" + orderId + " successfully marked as completed");
|
||||
} else {
|
||||
getLogger().warning("⚠️ Complete Order API returned code: " + responseCode);
|
||||
}
|
||||
}
|
||||
HttpURLConnection conn = openAuthConnection(wpUrlComplete, "POST");
|
||||
writeJson(conn, "{\"id\":\"" + orderId + "\"}");
|
||||
int code = conn.getResponseCode();
|
||||
if (debug) getLogger().info("Complete Order #" + orderId + " → HTTP " + code);
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "Complete Order Error for #" + orderId, e);
|
||||
getLogger().log(Level.WARNING, "Complete Order Error #" + orderId, e);
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(this);
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// /orders BEFEHL – Bestellhistorie im Chat
|
||||
// ===========================================================
|
||||
|
||||
private class OrdersCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("Nur für Spieler verfügbar.");
|
||||
return true;
|
||||
}
|
||||
Player p = (Player) sender;
|
||||
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
p.sendMessage(ChatColor.YELLOW + "📦 Deine letzten Bestellungen:");
|
||||
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String urlString = wpBase + "/orders_history?player=" + p.getName();
|
||||
HttpURLConnection conn = openAuthConnection(urlString, "GET");
|
||||
int code = conn.getResponseCode();
|
||||
|
||||
if (code == 200) {
|
||||
String body = readResponse(conn);
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
JsonArray orders = json.getAsJsonArray("orders");
|
||||
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
|
||||
if (orders == null || orders.size() == 0) {
|
||||
p.sendMessage(ChatColor.GRAY + "Noch keine Bestellungen vorhanden.");
|
||||
} else {
|
||||
for (int i = 0; i < Math.min(orders.size(), 10); i++) {
|
||||
JsonObject o = orders.get(i).getAsJsonObject();
|
||||
String title = o.get("item_title").getAsString();
|
||||
double price = o.get("price").getAsDouble();
|
||||
String status = o.get("status").getAsString();
|
||||
String date = o.has("created_at") ? o.get("created_at").getAsString().substring(0,10) : "?";
|
||||
|
||||
String statusColor;
|
||||
switch (status) {
|
||||
case "completed": statusColor = ChatColor.GREEN + "✔ Geliefert"; break;
|
||||
case "cancelled": statusColor = ChatColor.RED + "✘ Storniert"; break;
|
||||
case "processing": statusColor = ChatColor.AQUA + "⟳ In Arbeit"; break;
|
||||
default: statusColor = ChatColor.YELLOW + "⌛ Ausstehend"; break;
|
||||
}
|
||||
|
||||
// Titel kürzen wenn zu lang
|
||||
String display = title.length() > 35 ? title.substring(0, 32) + "…" : title;
|
||||
p.sendMessage(
|
||||
ChatColor.WHITE + " #" + o.get("id").getAsInt() +
|
||||
" " + ChatColor.AQUA + display +
|
||||
ChatColor.GRAY + " | " + ChatColor.YELLOW + price + " " + currency +
|
||||
ChatColor.GRAY + " | " + statusColor +
|
||||
ChatColor.GRAY + " (" + date + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
});
|
||||
} else if (code == 404) {
|
||||
// Endpunkt noch nicht implementiert
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
p.sendMessage(ChatColor.GRAY + "Bestellhistorie nicht verfügbar (WP-Plugin zu alt?)."));
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
p.sendMessage(ChatColor.RED + "Fehler beim Laden der Bestellungen (HTTP " + code + ")."));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "/orders Fehler", e);
|
||||
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () ->
|
||||
p.sendMessage(ChatColor.RED + "Verbindungsfehler zum Shop-Server."));
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(IngameShopSpigot.this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// HTTP HILFSMETHODEN
|
||||
// ===========================================================
|
||||
|
||||
/**
|
||||
* Öffnet eine HTTP-Verbindung mit gesetztem API-Key Header.
|
||||
*/
|
||||
private HttpURLConnection openAuthConnection(String urlString, String method) throws Exception {
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("X-WIS-Key", apiKey); // NEU: API-Key bei jedem Request
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
if ("POST".equals(method)) conn.setDoOutput(true);
|
||||
return conn;
|
||||
}
|
||||
|
||||
private void writeJson(HttpURLConnection conn, String json) throws Exception {
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
private String readResponse(HttpURLConnection conn) throws Exception {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) sb.append(line);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// ITEM PARSING
|
||||
// ===========================================================
|
||||
|
||||
private ItemStack parseItem(String itemId) {
|
||||
try {
|
||||
String cleanId = itemId.toUpperCase().trim().replace("MINECRAFT:", "").replace("MC:", "");
|
||||
String materialName = numericIdToMaterial(cleanId);
|
||||
if (materialName == null) materialName = cleanId;
|
||||
|
||||
if (materialName.equalsIgnoreCase("VIP") || materialName.startsWith("VIP")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String clean = itemId.toUpperCase().trim()
|
||||
.replace("MINECRAFT:", "").replace("MC:", "");
|
||||
String materialName = numericIdToMaterial(clean);
|
||||
if (materialName == null) materialName = clean;
|
||||
|
||||
if (materialName.equalsIgnoreCase("VIP") || materialName.startsWith("VIP")) return null;
|
||||
|
||||
Material material = Material.matchMaterial(materialName);
|
||||
if (material != null && material.isItem()) {
|
||||
return new ItemStack(material,1);
|
||||
}
|
||||
if (material != null && material.isItem()) return new ItemStack(material, 1);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.WARNING, "parseItem Fehler: " + itemId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String numericIdToMaterial(String id) {
|
||||
if (!id.matches("\\d+")) return null;
|
||||
switch (id) {
|
||||
case "352": return "BONE_MEAL";
|
||||
case "264": return "EMERALD";
|
||||
case "388": return "EMERALD_BLOCK";
|
||||
case "357": return "GOLDEN_APPLE";
|
||||
case "264": return "DIAMOND";
|
||||
case "388": return "EMERALD";
|
||||
case "357": return "COOKIE";
|
||||
case "322": return "GOLDEN_CARROT";
|
||||
case "348": return "GOLD_INGOT";
|
||||
case "133": return "EMERALD_ORE";
|
||||
case "41": return "GOLD_ORE";
|
||||
case "14": return "GOLD_BLOCK";
|
||||
case "289": return "COOKIE";
|
||||
case "1": return "STONE";
|
||||
case "5": return "PLANKS";
|
||||
case "17": return "OAK_LOG";
|
||||
case "260": return "BONE_BLOCK";
|
||||
case "287": return "TOTEM_OF_UNDYING";
|
||||
default: return null;
|
||||
case "133": return "EMERALD_BLOCK";
|
||||
case "41": return "GOLD_BLOCK";
|
||||
case "14": return "GOLD_ORE";
|
||||
case "289": return "GUNPOWDER";
|
||||
case "1": return "STONE";
|
||||
case "5": return "OAK_PLANKS";
|
||||
case "17": return "OAK_LOG";
|
||||
case "260": return "APPLE";
|
||||
case "287": return "STRING";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// DATA CLASS
|
||||
// ===========================================================
|
||||
|
||||
private static class OrderData {
|
||||
int id;
|
||||
int id;
|
||||
String itemId;
|
||||
String itemTitle;
|
||||
double price;
|
||||
int quantity;
|
||||
int quantity;
|
||||
String jsonPayload;
|
||||
|
||||
|
||||
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.id = id;
|
||||
this.itemId = itemId;
|
||||
this.itemTitle = itemTitle;
|
||||
this.price = price;
|
||||
this.quantity = quantity;
|
||||
this.jsonPayload = jsonPayload;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
getLogger().info("IngameShopSpigot gestoppt");
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
# Hier nur die Domain eingeben (ohne /wp-json am Ende)
|
||||
wordpress-url: "<Wordpress URL>"
|
||||
# IngameShopSpigot v6.3 Konfiguration
|
||||
# ==========================================
|
||||
|
||||
# WICHTIG: Der Name des Servers, wie er in WordPress hinterlegt ist (Slug).
|
||||
# Damit wird verhindert, dass ein Spieler auf Lobby etwas für Survival kauft und es dort erhält.
|
||||
server-name: "Lobby"
|
||||
# Deine WordPress-URL (kein abschließendes /)
|
||||
wordpress-url: "https://deine-domain.de"
|
||||
|
||||
# Name der Währung (ersetze Coins durch deinen Namen, z.B. Dollar, Points, etc.)
|
||||
currency-name: "Euro"
|
||||
# API-Key aus den WordPress-Einstellungen:
|
||||
# Ingame Shop → Einstellungen → 🔑 Spigot API-Key → Kopieren
|
||||
api-key: "HIER_DEN_KEY_AUS_WORDPRESS_EINTRAGEN"
|
||||
|
||||
# Alle wie viel Sekunden (ticks) nach neuen Bestellungen gesucht werden soll (Standard: 10 Sekunden)
|
||||
check-interval: 5
|
||||
# Name dieses Servers (muss mit dem Server-Slug in WordPress übereinstimmen)
|
||||
server-name: "survival"
|
||||
|
||||
# Debug Modus (true/false)
|
||||
debug-mode: true
|
||||
# Währungsname (muss mit WordPress-Einstellung übereinstimmen)
|
||||
currency-name: "Coins"
|
||||
|
||||
# Wie oft (in Sekunden) nach ausstehenden Bestellungen gesucht wird
|
||||
check-interval: 10
|
||||
|
||||
# Debug-Modus (ausführliche Logs in der Konsole)
|
||||
debug-mode: false
|
||||
@@ -1,25 +1,18 @@
|
||||
name: IngameShopSpigot
|
||||
version: 6.3
|
||||
main: de.mviper.spigot.IngameShopSpigot
|
||||
version: 1.0
|
||||
api-version: "1.20"
|
||||
api-version: 1.19
|
||||
depend: [Vault]
|
||||
description: Verbindet den WP Ingame Shop Pro mit dem Spigot-Server
|
||||
author: M_Viper
|
||||
description: Ingame-Shop Plugin (Spigot) empfPlugin-Messages vom Bungee und vergibt Items / zieht Geld via Vault.
|
||||
|
||||
# Vault ist optional, daher softdepend
|
||||
softdepend:
|
||||
- Vault
|
||||
|
||||
# Beispiel-Command (optional, kann in Java implementiert werden)
|
||||
commands:
|
||||
ingameshop:
|
||||
description: oder testet den IngameShop (Admin/Debug)
|
||||
usage: /ingameshop
|
||||
orders:
|
||||
description: Zeigt deine letzten Bestellungen im Shop an
|
||||
usage: /orders
|
||||
permission: ingameshop.orders
|
||||
|
||||
# Beispiel-Permissions (anpassbar)
|
||||
permissions:
|
||||
ingameshop.admin:
|
||||
description: Zugriff auf Admin-Funktionen des IngameShops
|
||||
default: op
|
||||
ingameshop.use:
|
||||
description: Basisrecht, um Shop-Features zu nutzen
|
||||
ingameshop.orders:
|
||||
description: Kann eigene Bestellhistorie einsehen
|
||||
default: true
|
||||
Reference in New Issue
Block a user