Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-05-14 22:09:22 +02:00
parent 88520bd34a
commit de0255965a
10 changed files with 239 additions and 563 deletions

View File

@@ -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<String, Object> 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<String, String> 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<String, Object> 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<String, Object> eco = new LinkedHashMap<>();
eco.put("balance", statusBalance);
eco.put("total_earned", ps.totalEarned);

View File

@@ -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 <give|take|set|check> <Spieler> [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 <Spieler>")));
sender.sendMessage(new TextComponent(c("&e/ecoadmin give <Spieler> <Betrag>")));
sender.sendMessage(new TextComponent(c("&e/ecoadmin take <Spieler> <Betrag>")));
sender.sendMessage(new TextComponent(c("&e/ecoadmin set <Spieler> <Betrag>")));
}
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) {}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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 <Spieler> <Betrag>
* Ü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 <Spieler> <Betrag>")));
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) {}
}

View File

@@ -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<Integer, java.util.List<String>> lineMap =
isAdmin ? adminLineMap : playerLineMap;
isAdmin ? adminLineMap : isSupporter ? supporterLineMap : playerLineMap;
for (java.util.Map.Entry<Integer, java.util.List<String>> entry : lineMap.entrySet()) {
boolean hasNews = false;
for (String v : entry.getValue()) {

View File

@@ -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 <Spieler> <Betrag>
ecoadmin:
description: Admin-Verwaltung des Economy-Systems
usage: /ecoadmin <give|take|set|check> <Spieler> [Betrag]
permission: economy.admin
aliases: [ecomod, moneyadmin]
# /pay und /ecoadmin werden von NexEco (Spigot) verwaltet
# ── VanishModule ──────────────────────────────────────────
vanish:

View File

@@ -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%