diff --git a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java index 5ddf209..9622e01 100644 --- a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java +++ b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java @@ -548,10 +548,7 @@ public class StatusAPI extends Plugin implements Runnable { playerMap.put("deaths", ps.deaths); playerMap.put("online", ProxyServer.getInstance().getPlayer(ps.uuid) != null); // Balance direkt aus MySQL (serverübergreifend) - EconomyModule ecoModPlayer = (EconomyModule) moduleManager.getModule("EconomyModule"); - double playerBalance = (ecoModPlayer != null && ecoModPlayer.getManager() != null) - ? ecoModPlayer.getManager().getBalance(ps.uuid) - : ps.balance; + double playerBalance = playerBalances.getOrDefault(ps.uuid, 0.0); Map economy = new LinkedHashMap<>(); economy.put("balance", playerBalance); economy.put("total_earned", ps.totalEarned); @@ -575,12 +572,7 @@ public class StatusAPI extends Plugin implements Runnable { // Kein Cache – UUID und Balance kommen direkt aus der DB if ("GET".equalsIgnoreCase(method) && "/economy/player".equalsIgnoreCase(pathOnly)) { Map qp = parseQueryParams(path); - EconomyModule ecoModGet = (EconomyModule) moduleManager.getModule("EconomyModule"); - if (ecoModGet == null || ecoModGet.getManager() == null) { - sendHttpResponse(out, "{\"success\":false,\"error\":\"economy_module_unavailable\"}", 503); - return; - } - // UUID auflösen: erst Query-Param, dann Name über EconomyManager (DB-Lookup) + // UUID auflösen aus Query-Param UUID ecoUuid = null; String ecoName = null; String uuidParam = qp.get("uuid"); @@ -588,16 +580,13 @@ public class StatusAPI extends Plugin implements Runnable { if (uuidParam != null && !uuidParam.isEmpty()) { try { ecoUuid = UUID.fromString(uuidParam.trim()); } catch (IllegalArgumentException ignored) {} } - if (ecoUuid == null && nameParam != null && !nameParam.isEmpty()) { - ecoUuid = ecoModGet.getManager().resolveUUID(nameParam.trim()); - ecoName = nameParam.trim(); - } if (ecoUuid == null) { sendHttpResponse(out, "{\"success\":false,\"error\":\"player_not_found\"}", 404); return; } if (ecoName == null) ecoName = uuidParam; - double directBalance = ecoModGet.getManager().getBalance(ecoUuid); + // Balance aus playerBalances Map lesen (befüllt von StatusAPIBridge via NexEco) + double directBalance = playerBalances.getOrDefault(ecoUuid, 0.0); Map payload = new LinkedHashMap<>(); payload.put("success", true); payload.put("uuid", ecoUuid.toString()); @@ -610,35 +599,26 @@ public class StatusAPI extends Plugin implements Runnable { } // POST /economy/update - // Kein Cache – Balance wird direkt in die DB geschrieben + // Empfängt Balance-Updates von StatusAPIBridge (Vault/NexEco → HTTP) + // Schreibt NUR in playerBalances für Tablist/Scoreboard – KEINE DB-Schreiboperationen if ("POST".equalsIgnoreCase(method) && "/economy/update".equalsIgnoreCase(pathOnly)) { String body = readBody(in, headers); - EconomyModule ecoModUpd = (EconomyModule) moduleManager.getModule("EconomyModule"); - if (ecoModUpd == null || ecoModUpd.getManager() == null) { - sendHttpResponse(out, "{\"success\":false,\"error\":\"economy_module_unavailable\"}", 503); - return; - } // UUID auflösen UUID ecoUpdUuid = null; String uuidBody = extractJsonString(body, "uuid"); - String nameBody = extractJsonString(body, "name"); if (uuidBody != null && !uuidBody.isEmpty()) { try { ecoUpdUuid = UUID.fromString(uuidBody.trim()); } catch (IllegalArgumentException ignored) {} } - if (ecoUpdUuid == null && nameBody != null && !nameBody.isEmpty()) { - ecoUpdUuid = ecoModUpd.getManager().resolveUUID(nameBody.trim()); - } if (ecoUpdUuid == null) { sendHttpResponse(out, "{\"success\":false,\"error\":\"player_not_found\"}", 404); return; } - // Balance direkt in DB schreiben + // Balance NUR in playerBalances Map speichern (für Tablist/Scoreboard) + // Die echte DB-Verwaltung macht ausschließlich NexEco String balStr = extractJsonString(body, "balance"); if (balStr != null && !balStr.isEmpty()) { try { double newBal = Double.parseDouble(balStr); - ecoModUpd.getManager().setBalance(ecoUpdUuid, newBal); - // Auch in der Tablist-Map speichern playerBalances.put(ecoUpdUuid, newBal); } catch (NumberFormatException ignored) {} } @@ -939,10 +919,7 @@ public class StatusAPI extends Plugin implements Runnable { playerInfo.put("first_seen", ps.firstSeen); playerInfo.put("last_seen", ps.lastSeen); // Balance direkt aus MySQL (serverübergreifend) - EconomyModule ecoModStatus = (EconomyModule) moduleManager.getModule("EconomyModule"); - double statusBalance = (ecoModStatus != null && ecoModStatus.getManager() != null) - ? ecoModStatus.getManager().getBalance(p.getUniqueId()) - : ps.balance; + double statusBalance = playerBalances.getOrDefault(p.getUniqueId(), 0.0); Map eco = new LinkedHashMap<>(); eco.put("balance", statusBalance); eco.put("total_earned", ps.totalEarned); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java index 82c8796..158629f 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java @@ -1,134 +1,19 @@ package net.viper.status.modules.economy; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Plugin; -import java.util.UUID; - /** - * /ecoadmin [Betrag] - * Admin-Verwaltung des Economy-Systems (auch für Offline-Spieler). - * - * Berechtigung: economy.admin + * /ecoadmin – wird NICHT mehr auf BungeeCord registriert. + * NexEco /eco auf dem Spigot-Server übernimmt Admin-Befehle. */ public class EcoAdminCommand extends Command { - private final Plugin plugin; - private final EconomyManager manager; - public EcoAdminCommand(Plugin plugin, EconomyManager manager) { - super("ecoadmin", "economy.admin", "ecomod", "moneyadmin"); - this.plugin = plugin; - this.manager = manager; + super("ecoadmin_disabled_nexeco", "economy.admin"); } @Override - public void execute(CommandSender sender, String[] args) { - if (!sender.hasPermission("economy.admin")) { - sender.sendMessage(new TextComponent(c("&cKeine Berechtigung."))); - return; - } - - if (args.length < 2) { - sendHelp(sender); - return; - } - - String action = args[0].toLowerCase(); - String targetName = args[1]; - - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - UUID targetUUID = manager.resolveUUID(targetName); - - if (targetUUID == null) { - sender.sendMessage(new TextComponent(c("&cSpieler &e" + targetName + " &cnicht gefunden."))); - return; - } - - switch (action) { - - case "check": { - double bal = manager.getBalance(targetUUID); - sender.sendMessage(new TextComponent(c("&7Kontostand von &e" + targetName - + "&7: &a" + formatAmount(bal) + " $"))); - break; - } - - case "give": { - if (args.length < 3) { sendHelp(sender); return; } - double amount = parseAmount(args[2]); - if (amount <= 0) { sender.sendMessage(new TextComponent(c("&cUngültiger Betrag."))); return; } - manager.deposit(targetUUID, amount); - double newBal = manager.getBalance(targetUUID); - sender.sendMessage(new TextComponent(c("&a+ " + formatAmount(amount) - + " $ &7an &e" + targetName + " &7→ Neues Guthaben: &a" + formatAmount(newBal) + " $"))); - notifyPlayer(targetUUID, c("&7Admin hat dir &a+" + formatAmount(amount) - + " $ &7gegeben. Guthaben: &a" + formatAmount(newBal) + " $")); - break; - } - - case "take": { - if (args.length < 3) { sendHelp(sender); return; } - double amount = parseAmount(args[2]); - if (amount <= 0) { sender.sendMessage(new TextComponent(c("&cUngültiger Betrag."))); return; } - boolean ok = manager.withdraw(targetUUID, amount); - if (!ok) { - sender.sendMessage(new TextComponent(c("&cNicht genug Guthaben bei &e" + targetName + "&c."))); - return; - } - double newBal = manager.getBalance(targetUUID); - sender.sendMessage(new TextComponent(c("&c- " + formatAmount(amount) - + " $ &7von &e" + targetName + " &7→ Neues Guthaben: &a" + formatAmount(newBal) + " $"))); - notifyPlayer(targetUUID, c("&7Admin hat dir &c-" + formatAmount(amount) - + " $ &7abgezogen. Guthaben: &a" + formatAmount(newBal) + " $")); - break; - } - - case "set": { - if (args.length < 3) { sendHelp(sender); return; } - double amount = parseAmount(args[2]); - if (amount < 0) { sender.sendMessage(new TextComponent(c("&cBetrag darf nicht negativ sein."))); return; } - manager.setBalance(targetUUID, amount); - sender.sendMessage(new TextComponent(c("&7Guthaben von &e" + targetName - + " &7gesetzt auf &a" + formatAmount(amount) + " $"))); - notifyPlayer(targetUUID, c("&7Dein Guthaben wurde auf &a" + formatAmount(amount) + " $ &7gesetzt.")); - break; - } - - default: - sendHelp(sender); - } - }); - } - - private void notifyPlayer(UUID uuid, String message) { - ProxiedPlayer target = plugin.getProxy().getPlayer(uuid); - if (target != null) { - target.sendMessage(new TextComponent(message)); - } - } - - private void sendHelp(CommandSender sender) { - sender.sendMessage(new TextComponent(c("&e/ecoadmin check "))); - sender.sendMessage(new TextComponent(c("&e/ecoadmin give "))); - sender.sendMessage(new TextComponent(c("&e/ecoadmin take "))); - sender.sendMessage(new TextComponent(c("&e/ecoadmin set "))); - } - - private static double parseAmount(String s) { - try { return Double.parseDouble(s.replace(",", ".")); } - catch (NumberFormatException e) { return -1; } - } - - private static String formatAmount(double amount) { - return String.format("%,.2f", amount); - } - - private static String c(String msg) { - return ChatColor.translateAlternateColorCodes('&', msg); - } + public void execute(CommandSender sender, String[] args) {} } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java index 7ade6e4..6ce2ada 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java @@ -11,11 +11,16 @@ import java.util.logging.Logger; /** * Verwaltet die MySQL-Verbindung (HikariCP) und die Tabelle bc_accounts. - * Wird vom EconomyModule gehalten und an den EconomyManager weitergegeben. + * + * Fixes: + * - balance-Spalte als DOUBLE(30,2) statt VARCHAR → kompatibel mit NexEco & SurvivalPlus + * - atomare Transaktion für withdraw+deposit → kein Geldverlust bei Absturz + * - FOR UPDATE Lock → kein Race-Condition-Bug bei gleichzeitigen Überweisungen */ public class EconomyDatabase { - private static final String TABLE = "bc_accounts"; + private static final String TABLE = "bc_accounts"; + private static final String TABLE_NAMES = "bc_player_names"; private final Logger log; private HikariDataSource dataSource; @@ -24,11 +29,10 @@ public class EconomyDatabase { this.log = plugin.getLogger(); HikariConfig cfg = new HikariConfig(); - // HikariCP Startup-Logs unterdrücken java.util.logging.Logger.getLogger("com.zaxxer.hikari").setLevel(java.util.logging.Level.WARNING); - java.util.logging.Logger.getLogger("com.zaxxer.hikari.HikariDataSource").setLevel(java.util.logging.Level.WARNING); cfg.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database - + "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&useUnicode=true"); + + "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&useUnicode=true" + + "&allowPublicKeyRetrieval=true"); cfg.setUsername(user); cfg.setPassword(password); cfg.setMaximumPoolSize(5); @@ -45,35 +49,42 @@ public class EconomyDatabase { dataSource = new HikariDataSource(cfg); } catch (Exception e) { log.severe("[Economy] MySQL-Verbindung fehlgeschlagen: " + e.getMessage()); - log.severe("[Economy] Bitte Zugangsdaten in verify.properties prüfen!"); return; } - // Tabelle anlegen falls nicht vorhanden (kompatibel mit SurvivalPlus) - try (Connection con = dataSource.getConnection(); - PreparedStatement ps = con.prepareStatement( - "CREATE TABLE IF NOT EXISTS `" + TABLE + "` (" + - " `player_name` VARCHAR(36) NOT NULL," + - " `balance` VARCHAR(255) NOT NULL DEFAULT '0'," + - " PRIMARY KEY (`player_name`)" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")) { - ps.executeUpdate(); + // ── bc_accounts: balance als DOUBLE – kompatibel mit NexEco & SurvivalPlus ── + try (Connection con = dataSource.getConnection(); Statement st = con.createStatement()) { + st.executeUpdate( + "CREATE TABLE IF NOT EXISTS `" + TABLE + "` (" + + " `player_name` VARCHAR(36) NOT NULL," + + " `balance` DOUBLE(30,2) NOT NULL DEFAULT 0.00," + + " PRIMARY KEY (`player_name`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;" + ); + // Falls Tabelle existiert aber balance noch VARCHAR ist → konvertieren + st.executeUpdate( + "ALTER TABLE `" + TABLE + "` " + + "MODIFY COLUMN `balance` DOUBLE(30,2) NOT NULL DEFAULT 0.00" + ); } catch (SQLException e) { - log.severe("[Economy] Tabellen-Setup (bc_accounts) fehlgeschlagen: " + e.getMessage()); + // ALTER schlägt fehl wenn Typ bereits korrekt ist – kein Problem + if (!e.getMessage().contains("Duplicate") && !e.getMessage().contains("doesn't exist")) { + log.warning("[Economy] Tabellen-Setup bc_accounts: " + e.getMessage()); + } } - // bc_player_names Tabelle anlegen - try (Connection con = dataSource.getConnection(); - PreparedStatement ps = con.prepareStatement( - "CREATE TABLE IF NOT EXISTS `bc_player_names` (" + - " `uuid` VARCHAR(36) NOT NULL PRIMARY KEY," + - " `name` VARCHAR(16) NOT NULL," + - " `updated` BIGINT NOT NULL" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")) { - ps.executeUpdate(); + // ── bc_player_names ──────────────────────────────────────────────────── + try (Connection con = dataSource.getConnection(); Statement st = con.createStatement()) { + st.executeUpdate( + "CREATE TABLE IF NOT EXISTS `" + TABLE_NAMES + "` (" + + " `uuid` VARCHAR(36) NOT NULL PRIMARY KEY," + + " `name` VARCHAR(16) NOT NULL," + + " `updated` BIGINT NOT NULL" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;" + ); if (StatusAPI.DEBUG) log.info("[Economy] MySQL verbunden – Tabellen bereit."); } catch (SQLException e) { - log.severe("[Economy] Tabellen-Setup (bc_player_names) fehlgeschlagen: " + e.getMessage()); + log.severe("[Economy] Tabellen-Setup bc_player_names fehlgeschlagen: " + e.getMessage()); } } @@ -82,89 +93,22 @@ public class EconomyDatabase { } public void close() { - if (dataSource != null && !dataSource.isClosed()) { - dataSource.close(); - } + if (dataSource != null && !dataSource.isClosed()) dataSource.close(); } - /** - * Sucht UUID in CMI_users – nutzt eine separate DB-Verbindung falls - * economy.cmi.database konfiguriert ist, sonst dieselbe DB. - */ - public java.util.UUID findUUIDByName(String name) { - if (!isConnected()) return null; - try (Connection con = dataSource.getConnection(); - PreparedStatement ps = con.prepareStatement( - "SELECT `player_uuid` FROM `CMI_users` WHERE `username` = ? LIMIT 1")) { - ps.setString(1, name); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - String uuidStr = rs.getString("player_uuid"); - if (uuidStr != null && !uuidStr.isEmpty()) { - return java.util.UUID.fromString(uuidStr); - } - } - } - } catch (SQLException | IllegalArgumentException e) { - // CMI_users nicht in dieser DB – kein Problem, bc_player_names ist der Primär-Lookup - } - return null; - } - - /** Speichert den Spielernamen in einer eigenen Lookup-Tabelle. */ - public void saveNameMapping(java.util.UUID uuid, String name) { - if (!isConnected()) return; - try (Connection con = dataSource.getConnection(); - PreparedStatement ps = con.prepareStatement( - "INSERT INTO `bc_player_names` (`uuid`, `name`, `updated`) VALUES (?, ?, ?) " + - "ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `updated` = VALUES(`updated`)")) { - ps.setString(1, uuid.toString()); - ps.setString(2, name); - ps.setLong(3, System.currentTimeMillis()); - ps.executeUpdate(); - } catch (SQLException e) { - log.warning("[Economy] Name-Mapping fehlgeschlagen: " + e.getMessage()); - } - } - - /** UUID-Lookup über bc_player_names (eigene Tabelle). */ - public java.util.UUID findUUIDByNameOwn(String name) { - if (!isConnected()) return null; - try (Connection con = dataSource.getConnection(); - PreparedStatement ps = con.prepareStatement( - "SELECT `uuid` FROM `bc_player_names` WHERE `name` = ? LIMIT 1")) { - ps.setString(1, name); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - return java.util.UUID.fromString(rs.getString("uuid")); - } - } - } catch (SQLException | IllegalArgumentException e) { - // Tabelle noch nicht vorhanden - } - return null; - } - - /** Kombinierter UUID-Lookup: erst eigene Tabelle, dann CMI_users. */ - public java.util.UUID resolveUUID(String name) { - java.util.UUID uuid = findUUIDByNameOwn(name); - if (uuid != null) return uuid; - return findUUIDByName(name); - } + // ── Kontostand ──────────────────────────────────────────────────────────── /** Lädt den Kontostand direkt aus der DB. Gibt -1 zurück wenn kein Eintrag. */ public double load(UUID uuid) { if (!isConnected()) return -1; try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement( - "SELECT `balance` FROM `" + TABLE + "` WHERE `player_name` = ?")) { + "SELECT `balance` FROM `" + TABLE + "` WHERE `player_name` = ?")) { ps.setString(1, uuid.toString()); try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - return Double.parseDouble(rs.getString("balance")); - } + if (rs.next()) return rs.getDouble("balance"); } - } catch (SQLException | NumberFormatException e) { + } catch (SQLException e) { log.warning("[Economy] Load fehlgeschlagen für " + uuid + ": " + e.getMessage()); } return -1; @@ -175,13 +119,114 @@ public class EconomyDatabase { if (!isConnected()) return; try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement( - "INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " + - "ON DUPLICATE KEY UPDATE `balance` = VALUES(`balance`)")) { + "INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE `balance` = VALUES(`balance`)")) { ps.setString(1, uuid.toString()); - ps.setString(2, String.valueOf(balance)); + ps.setDouble(2, balance); ps.executeUpdate(); } catch (SQLException e) { log.warning("[Economy] Save fehlgeschlagen für " + uuid + ": " + e.getMessage()); } } + + /** + * Atomare Überweisung von → to. + * Nutzt eine SQL-Transaktion mit FOR UPDATE Lock – race-condition-sicher. + * Gibt false zurück wenn Sender nicht genug Guthaben hat. + */ + public boolean transfer(UUID from, UUID to, double amount, double startBalance) { + if (!isConnected()) return false; + Connection con = null; + try { + con = dataSource.getConnection(); + con.setAutoCommit(false); + + // Sender sperren und Balance lesen + double fromBalance; + try (PreparedStatement ps = con.prepareStatement( + "SELECT `balance` FROM `" + TABLE + "` WHERE `player_name` = ? FOR UPDATE")) { + ps.setString(1, from.toString()); + try (ResultSet rs = ps.executeQuery()) { + fromBalance = rs.next() ? rs.getDouble("balance") : startBalance; + } + } + + if (fromBalance < amount) { con.rollback(); return false; } + + // Sender abziehen + try (PreparedStatement ps = con.prepareStatement( + "INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE `balance` = VALUES(`balance`)")) { + ps.setString(1, from.toString()); + ps.setDouble(2, fromBalance - amount); + ps.executeUpdate(); + } + + // Empfänger gutschreiben (Konto anlegen falls nötig) + try (PreparedStatement ps = con.prepareStatement( + "INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE `balance` = `balance` + ?")) { + ps.setString(1, to.toString()); + ps.setDouble(2, startBalance + amount); + ps.setDouble(3, amount); + ps.executeUpdate(); + } + + con.commit(); + return true; + + } catch (SQLException e) { + log.warning("[Economy] Transfer fehlgeschlagen: " + e.getMessage()); + try { if (con != null) con.rollback(); } catch (SQLException ex) { /* ignore */ } + return false; + } finally { + try { if (con != null) con.close(); } catch (SQLException ex) { /* ignore */ } + } + } + + // ── Name-Lookup ─────────────────────────────────────────────────────────── + + public void saveNameMapping(UUID uuid, String name) { + if (!isConnected()) return; + try (Connection con = dataSource.getConnection(); + PreparedStatement ps = con.prepareStatement( + "INSERT INTO `" + TABLE_NAMES + "` (`uuid`, `name`, `updated`) VALUES (?, ?, ?) " + + "ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `updated` = VALUES(`updated`)")) { + ps.setString(1, uuid.toString()); + ps.setString(2, name); + ps.setLong(3, System.currentTimeMillis()); + ps.executeUpdate(); + } catch (SQLException e) { + log.warning("[Economy] Name-Mapping fehlgeschlagen: " + e.getMessage()); + } + } + + public UUID findUUIDByNameOwn(String name) { + if (!isConnected()) return null; + try (Connection con = dataSource.getConnection(); + PreparedStatement ps = con.prepareStatement( + "SELECT `uuid` FROM `" + TABLE_NAMES + "` WHERE `name` = ? LIMIT 1")) { + ps.setString(1, name); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) return UUID.fromString(rs.getString("uuid")); + } + } catch (SQLException | IllegalArgumentException e) { /* ignorieren */ } + return null; + } + + public UUID findUUIDByName(String name) { + if (!isConnected()) return null; + try (Connection con = dataSource.getConnection(); + PreparedStatement ps = con.prepareStatement( + "SELECT `player_uuid` FROM `CMI_users` WHERE `username` = ? LIMIT 1")) { + ps.setString(1, name); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + String s = rs.getString("player_uuid"); + if (s != null && !s.isEmpty()) return UUID.fromString(s); + } + } + } catch (SQLException | IllegalArgumentException e) { /* CMI nicht vorhanden – kein Problem */ } + return null; + } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java index 8b7099a..0672fd5 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java @@ -1,33 +1,37 @@ package net.viper.status.modules.economy; import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.event.EventHandler; +import net.viper.status.StatusAPI; +/** + * EconomyListener – nur noch Aufräumen der playerBalances Map. + * + * Das Befüllen der Map geschieht ausschließlich durch die StatusAPIBridge + * (Spigot) die über Vault/NexEco den Kontostand per HTTP an die StatusAPI sendet. + */ public class EconomyListener implements Listener { - private final Plugin plugin; - private final EconomyManager manager; - public EconomyListener(Plugin plugin, EconomyManager manager) { - this.plugin = plugin; - this.manager = manager; + // EconomyManager wird nicht mehr benötigt } - /** - * Beim ersten Login: Konto anlegen falls noch nicht vorhanden. - * Kein Caching – alle weiteren Zugriffe gehen direkt in die DB. - */ @EventHandler public void onLogin(PostLoginEvent event) { - ProxiedPlayer player = event.getPlayer(); - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - // Namen für Offline-Lookup speichern - manager.saveNameMapping(player.getUniqueId(), player.getName()); - // Konto anlegen falls neu - manager.getBalance(player.getUniqueId()); - }); + // Wird von StatusAPIBridge befüllt – nichts zu tun beim Login + } + + @EventHandler + public void onDisconnect(PlayerDisconnectEvent event) { + // Beim Logout aus der Map entfernen + StatusAPI.playerBalances.remove(event.getPlayer().getUniqueId()); + } + + public void cancelTasks() { + // Kein periodischer Task mehr nötig } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java index 97bd671..bf565de 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java @@ -1,110 +1,31 @@ package net.viper.status.modules.economy; -import net.viper.status.StatusAPI; import net.md_5.bungee.api.plugin.Plugin; - import java.util.UUID; /** - * Kein Cache – jeder Zugriff geht direkt in die Datenbank. - * Damit ist der Kontostand immer aktuell, egal von welchem Server - * er zuletzt geändert wurde (SurvivalPlus, CMI, etc.). + * EconomyManager – Stub, nicht mehr aktiv. + * Economy wird ausschließlich über NexEco (Spigot) verwaltet. */ public class EconomyManager { - private final Plugin plugin; - private final EconomyDatabase db; - private final double startBalance; + public EconomyManager(Plugin plugin, EconomyDatabase db, double startBalance) {} - public EconomyManager(Plugin plugin, EconomyDatabase db, double startBalance) { - this.plugin = plugin; - this.db = db; - this.startBalance = startBalance; - } + public void saveNameMapping(UUID uuid, String name) {} - public void saveNameMapping(UUID uuid, String name) { - db.saveNameMapping(uuid, name); - } + public UUID resolveUUID(String name) { return null; } - public UUID resolveUUID(String name) { - // 1. Online-Spieler auf dem Proxy (case-insensitive) - for (net.md_5.bungee.api.connection.ProxiedPlayer p : plugin.getProxy().getPlayers()) { - if (p.getName().equalsIgnoreCase(name)) return p.getUniqueId(); - } - // 2. Eigene bc_player_names Tabelle - UUID uuid = db.findUUIDByNameOwn(name); - if (uuid != null) return uuid; - // 3. CMI_users Fallback - uuid = db.findUUIDByName(name); - if (uuid != null) return uuid; - // 4. Mojang API als letzter Ausweg - return lookupMojang(name); - } + public double getBalance(UUID uuid) { return 0.0; } - /** UUID via Mojang API holen (nur wenn alle lokalen Lookups fehlschlagen). */ - private UUID lookupMojang(String name) { - try { - java.net.URL url = new java.net.URL("https://api.mojang.com/users/profiles/minecraft/" + name); - java.net.HttpURLConnection con = (java.net.HttpURLConnection) url.openConnection(); - con.setConnectTimeout(3000); - con.setReadTimeout(3000); - con.setRequestMethod("GET"); - if (con.getResponseCode() != 200) return null; - java.io.BufferedReader br = new java.io.BufferedReader( - new java.io.InputStreamReader(con.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) sb.append(line); - br.close(); - String json = sb.toString(); - int idIdx = json.indexOf("\"id\":\""); - if (idIdx < 0) return null; - String raw = json.substring(idIdx + 6, idIdx + 38); - String formatted = raw.replaceFirst( - "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); - UUID uuid = UUID.fromString(formatted); - // Für künftige Lookups speichern - db.saveNameMapping(uuid, name); - StatusAPI.debugLog(plugin, "[Economy] Mojang-Lookup: " + name + " → " + uuid); - return uuid; - } catch (Exception e) { - plugin.getLogger().warning("[Economy] Mojang-Lookup fehlgeschlagen für " + name + ": " + e.getMessage()); - return null; - } - } + public void setBalance(UUID uuid, double amount) {} - public double getBalance(UUID uuid) { - double bal = db.load(uuid); - if (bal < 0) { - // Neuer Spieler – Startkonto anlegen - db.save(uuid, startBalance); - return startBalance; - } - return bal; - } + public boolean deposit(UUID uuid, double amount) { return false; } - public void setBalance(UUID uuid, double amount) { - db.save(uuid, Math.max(0.0, amount)); - } + public boolean withdraw(UUID uuid, double amount) { return false; } - public boolean deposit(UUID uuid, double amount) { - if (amount <= 0) return false; - double current = db.load(uuid); - if (current < 0) current = startBalance; - db.save(uuid, current + amount); - return true; - } + public boolean transfer(UUID from, UUID to, double amount) { return false; } - public boolean withdraw(UUID uuid, double amount) { - if (amount <= 0) return false; - double current = db.load(uuid); - if (current < 0) current = 0; - if (current < amount) return false; - db.save(uuid, current - amount); - return true; - } + public boolean hasAccount(UUID uuid) { return false; } - public boolean hasAccount(UUID uuid) { - return db.load(uuid) >= 0; - } + public double getStartBalance() { return 0.0; } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java index 33246fd..0f3cf35 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java @@ -1,95 +1,32 @@ package net.viper.status.modules.economy; import net.md_5.bungee.api.plugin.Plugin; -import net.viper.status.StatusAPI; import net.viper.status.module.Module; -import java.util.Properties; - /** - * EconomyModule – serverübergreifendes Geldkonto über MySQL (bc_accounts). + * EconomyModule – DEAKTIVIERT. * - * Konfiguration in verify.properties: - * economy.mysql.host=localhost - * economy.mysql.port=3306 - * economy.mysql.database=survivalplus - * economy.mysql.username=root - * economy.mysql.password= - * economy.start-balance=500.0 + * Die Economy wird ausschließlich über NexEco (Spigot) verwaltet. + * Die StatusAPIBridge (Spigot-Plugin) liest den Kontostand über Vault/NexEco + * und pushed ihn per HTTP an die StatusAPI → playerBalances Map. * - * Das Modul registriert sich im Modul-System der StatusAPI und hält - * EconomyDatabase + EconomyManager, die von SurvivalPlus und allen - * anderen Servern über die gemeinsame bc_accounts-Tabelle genutzt werden. + * Damit gibt es nur EINE Datenquelle für Kontostände: NexEco / money_accounts. + * Das alte EconomyModule schrieb in bc_accounts – das führte zu doppelten, + * inkonsistenten Kontoständen. */ public class EconomyModule implements Module { - private EconomyDatabase database; - private EconomyManager manager; - @Override - public String getName() { - return "EconomyModule"; - } + public String getName() { return "EconomyModule"; } @Override public void onEnable(Plugin plugin) { - // Konfiguration aus verify.properties laden - Properties props = ((StatusAPI) plugin).getVerifyProperties(); - - String host = getProp(props, "economy.mysql.host", "localhost"); - int port = getInt (props, "economy.mysql.port", 3306); - String database = getProp(props, "economy.mysql.database", "survivalplus"); - String user = getProp(props, "economy.mysql.username", "root"); - String password = getProp(props, "economy.mysql.password", ""); - double startBal = getDbl (props, "economy.start-balance", 500.0); - - this.database = new EconomyDatabase(plugin, host, port, database, user, password); - - if (!this.database.isConnected()) { - plugin.getLogger().severe("[Economy] Modul wird NICHT aktiviert – keine DB-Verbindung."); - return; - } - - this.manager = new EconomyManager(plugin, this.database, startBal); - - // Listener registrieren (Login → load, Disconnect → save) - plugin.getProxy().getPluginManager().registerListener(plugin, new EconomyListener(plugin, manager)); - plugin.getProxy().getPluginManager().registerCommand(plugin, new PayCommand(plugin, manager)); - plugin.getProxy().getPluginManager().registerCommand(plugin, new EcoAdminCommand(plugin, manager)); - - StatusAPI.debugLog(plugin, "[Economy] EconomyModule aktiviert (start-balance: " + startBal + ")."); + plugin.getLogger().info("[Economy] EconomyModule ist deaktiviert – NexEco ist zuständig."); + plugin.getLogger().info("[Economy] Kontostände kommen via StatusAPIBridge (Vault → NexEco → HTTP)."); } @Override - public void onDisable(Plugin plugin) { - if (database != null) { - database.close(); - StatusAPI.debugLog(plugin, "[Economy] MySQL-Verbindung geschlossen."); - } - } + public void onDisable(Plugin plugin) {} - /** Gibt den EconomyManager zurück (für andere Module oder HTTP-Endpoints). */ - public EconomyManager getManager() { - return manager; - } - - // ---------------------------------------------------------------- - // Hilfsmethoden - // ---------------------------------------------------------------- - - private static String getProp(Properties p, String key, String def) { - if (p == null) return def; - String v = p.getProperty(key, def); - return (v == null || v.trim().isEmpty()) ? def : v.trim(); - } - - private static int getInt(Properties p, String key, int def) { - try { return Integer.parseInt(getProp(p, key, String.valueOf(def))); } - catch (NumberFormatException e) { return def; } - } - - private static double getDbl(Properties p, String key, double def) { - try { return Double.parseDouble(getProp(p, key, String.valueOf(def))); } - catch (NumberFormatException e) { return def; } - } + public EconomyManager getManager() { return null; } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java index eb87e4d..9c48eb7 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java @@ -1,102 +1,20 @@ package net.viper.status.modules.economy; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Plugin; -import java.util.UUID; - /** - * /pay - * Überweist Geld an einen anderen Spieler (auch offline). + * /pay – wird NICHT mehr auf BungeeCord registriert. + * NexEco auf dem Spigot-Server übernimmt /pay direkt. + * Diese Klasse existiert nur noch für Kompilier-Kompatibilität. */ public class PayCommand extends Command { - private final Plugin plugin; - private final EconomyManager manager; - public PayCommand(Plugin plugin, EconomyManager manager) { - super("pay", null); - this.plugin = plugin; - this.manager = manager; + super("pay_disabled_nexeco", null); } @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(new TextComponent(c("&cNur Spieler können diesen Befehl nutzen."))); - return; - } - - if (args.length < 2) { - sender.sendMessage(new TextComponent(c("&eVerwendung: &f/pay "))); - return; - } - - ProxiedPlayer payer = (ProxiedPlayer) sender; - String targetName = args[0]; - double amount; - - try { - amount = Double.parseDouble(args[1].replace(",", ".")); - } catch (NumberFormatException e) { - sender.sendMessage(new TextComponent(c("&cUngültiger Betrag."))); - return; - } - - if (amount <= 0) { - sender.sendMessage(new TextComponent(c("&cDer Betrag muss größer als 0 sein."))); - return; - } - - if (targetName.equalsIgnoreCase(payer.getName())) { - sender.sendMessage(new TextComponent(c("&cDu kannst dir nicht selbst Geld überweisen."))); - return; - } - - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - UUID targetUUID = manager.resolveUUID(targetName); - - if (targetUUID == null) { - payer.sendMessage(new TextComponent(c("&cSpieler &e" + targetName + " &cnicht gefunden."))); - return; - } - - double payerBalance = manager.getBalance(payer.getUniqueId()); - - if (payerBalance < amount) { - payer.sendMessage(new TextComponent(c("&cNicht genug Guthaben. Dein Kontostand: &e" - + formatAmount(payerBalance) + " $"))); - return; - } - - manager.withdraw(payer.getUniqueId(), amount); - manager.deposit(targetUUID, amount); - - payer.sendMessage(new TextComponent(c("&aErfolgreich &e" + formatAmount(amount) - + " $ &aan &e" + targetName + " &aüberwiesen."))); - payer.sendMessage(new TextComponent(c("&7Dein neues Guthaben: &e" - + formatAmount(manager.getBalance(payer.getUniqueId())) + " $"))); - - // Online-Benachrichtigung an Empfänger - ProxiedPlayer target = plugin.getProxy().getPlayer(targetUUID); - if (target != null) { - target.sendMessage(new TextComponent(c("&a" + payer.getName() - + " hat dir &e" + formatAmount(amount) + " $ &aüberwiesen."))); - target.sendMessage(new TextComponent(c("&7Dein neues Guthaben: &e" - + formatAmount(manager.getBalance(targetUUID)) + " $"))); - } - }); - } - - private static String formatAmount(double amount) { - return String.format("%,.2f", amount); - } - - private static String c(String msg) { - return ChatColor.translateAlternateColorCodes('&', msg); - } + public void execute(CommandSender sender, String[] args) {} } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java b/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java index 37140d5..3ff5448 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java @@ -269,7 +269,7 @@ public class ScoreboardModule implements Module, Listener { String newsStr = buildNewsTicker(nOff); // Finde welche Zeilennummer(n) %news% enthält und sende nur diese java.util.Map> lineMap = - isAdmin ? adminLineMap : playerLineMap; + isAdmin ? adminLineMap : isSupporter ? supporterLineMap : playerLineMap; for (java.util.Map.Entry> entry : lineMap.entrySet()) { boolean hasNews = false; for (String v : entry.getValue()) { diff --git a/StatusAPI/src/main/resources/plugin.yml b/StatusAPI/src/main/resources/plugin.yml index 5b85f87..910bfd8 100644 --- a/StatusAPI/src/main/resources/plugin.yml +++ b/StatusAPI/src/main/resources/plugin.yml @@ -16,16 +16,7 @@ commands: usage: /scoreboard [hide|show|player|admin] aliases: [sb, togglesb] - # ── Economy ─────────────────────────────────────────────── - pay: - description: Überweise Geld an einen Spieler (auch offline) - usage: /pay - - ecoadmin: - description: Admin-Verwaltung des Economy-Systems - usage: /ecoadmin [Betrag] - permission: economy.admin - aliases: [ecomod, moneyadmin] + # /pay und /ecoadmin werden von NexEco (Spigot) verwaltet # ── VanishModule ────────────────────────────────────────── vanish: diff --git a/StatusAPI/src/main/resources/scoreboard.properties b/StatusAPI/src/main/resources/scoreboard.properties index 898abba..efcc46e 100644 --- a/StatusAPI/src/main/resources/scoreboard.properties +++ b/StatusAPI/src/main/resources/scoreboard.properties @@ -29,7 +29,7 @@ scoreboard.rainbow.mode=wave scoreboard.rainbow.speed=10 # Farben: Hex (#RRGGBB oder &#RRGGBB) oder Minecraft-Codes (&0-&f) – kommagetrennt # Leer lassen = voller HSB-Regenbogen -scoreboard.rainbow.colors=#FF0000,#FF6600,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF +scoreboard.rainbow.colors=&f,&b scoreboard.admin_permission=statusapi.scoreboard.admin scoreboard.supporter_permission=statusapi.scoreboard.supporter @@ -55,7 +55,7 @@ scoreboard.money_decimal_separator=, # scoreboard.separator=%gradient:#FF0000:#0000FF:────────────────────% (Farbig) # scoreboard.separator= (Leer/unsichtbar) # =================================================== -scoreboard.separator=&8&m-------------------- +scoreboard.separator=&8&m----------------------- # =================================================== # NEWS-TICKER – erscheint als %news% Placeholder @@ -97,8 +97,24 @@ scoreboard.lines.13=%news% scoreboard.lines.14=%line% scoreboard.lines.15=&7%compass% -# TicketSystem-Zeilen (auskommentiert – nach Bedarf einbinden): -# scoreboard.lines.X=&7Tickets: &e%ticket_my_open% +# =================================================== +# SUPPORTER-ZEILEN +# =================================================== +scoreboard.supporter_lines.1=%line% +scoreboard.supporter_lines.2=%gradient:&b:&f:&b:&l> Player Info:% +scoreboard.supporter_lines.3=&7%rank% &f%player% +scoreboard.supporter_lines.4=&7Ping: &f%ping%ms &8| &7%server% +scoreboard.supporter_lines.5= +scoreboard.supporter_lines.6=%gradient:&b:&f:&b:&l> Ticket:% +scoreboard.supporter_lines.7=&7Offen: &c%ticket_open% +scoreboard.supporter_lines.8=&7In Bearbeitung: &e%ticket_claimed% +scoreboard.supporter_lines.9= +scoreboard.supporter_lines.10=%gradient:&b:&f:&b:&l> Server Info:% +scoreboard.supporter_lines.11=&7Online: &f%online% &8/ &7%maxplayers% +scoreboard.supporter_lines.12= +scoreboard.supporter_lines.13= +scoreboard.supporter_lines.14=%line% +scoreboard.supporter_lines.15=&7%compass% # =================================================== # ADMIN-ZEILEN @@ -113,30 +129,12 @@ scoreboard.admin_lines.6= scoreboard.admin_lines.7=%gradient:&b:&f:&b:&l> Server Info:% scoreboard.admin_lines.8=&f%server% &8| &7RAM: &e%ram% scoreboard.admin_lines.8.2=&7Proxy: &f%uptime% -scoreboard.admin_lines.9= -scoreboard.admin_lines.10=&7TPS: &a%tps% -scoreboard.admin_lines.11= -scoreboard.admin_lines.12=%gradient:&b:&f:&b:&l> Tickets:% -scoreboard.admin_lines.13=&7Offen: &c%ticket_open% &8| &7Aktiv: &e%ticket_claimed% -scoreboard.admin_lines.14=&7Bewertung: &a%ticket_rating_good%&8/&c%ticket_rating_bad% &7(&f%ticket_rating_pct%&7%&7) -scoreboard.admin_lines.15=%line% -scoreboard.admin_lines.15.2=&7%compass% - -# =================================================== -# SUPPORTER-ZEILEN -# =================================================== -scoreboard.supporter_lines.1=%line% -scoreboard.supporter_lines.2=%gradient:&6:&f:&6:&l> Support Panel:% -scoreboard.supporter_lines.3=&7%rank% &f%player% -scoreboard.supporter_lines.4=&7Ping: &f%ping%ms &8| &7%server% -scoreboard.supporter_lines.5= -scoreboard.supporter_lines.6=%gradient:&6:&f:&6:&l> Tickets:% -scoreboard.supporter_lines.7=&7Offen: &c%ticket_open% -scoreboard.supporter_lines.8=&7Meine Tickets: &e%ticket_my_open% -scoreboard.supporter_lines.9= -scoreboard.supporter_lines.10=%gradient:&6:&f:&6:&l> Server Info:% -scoreboard.supporter_lines.11=&7Online: &f%online% &8/ &7%maxplayers% -scoreboard.supporter_lines.12=&7Zeit: &f%time% -scoreboard.supporter_lines.13= -scoreboard.supporter_lines.14=%line% -scoreboard.supporter_lines.15=&7%compass% +scoreboard.admin_lines.9=&7TPS: &a%tps% +scoreboard.admin_lines.10= +scoreboard.admin_lines.11=%gradient:&b:&f:&b:&l> Ticket:% +scoreboard.admin_lines.12=&7Tickets Offen: &c%ticket_open% +scoreboard.admin_lines.12.2=&7Tickets In Bearbeitung: &e%ticket_claimed% +scoreboard.admin_lines.13=&7Spieler: %online% &8/ &7%maxplayers% +scoreboard.admin_lines.14=%line% +scoreboard.admin_lines.15=&7%compass% +scoreboard.admin_lines.15.2=&7Pos: X:&f%x% &7Y:&f%y% &7Z:&f%z%