Upload folder via GUI - src
This commit is contained in:
@@ -7,6 +7,7 @@ import net.md_5.bungee.api.plugin.Plugin;
|
|||||||
import net.viper.status.module.ModuleManager;
|
import net.viper.status.module.ModuleManager;
|
||||||
import net.viper.status.modules.economy.EconomyModule;
|
import net.viper.status.modules.economy.EconomyModule;
|
||||||
import net.viper.status.modules.tablist.TablistModule;
|
import net.viper.status.modules.tablist.TablistModule;
|
||||||
|
import net.viper.status.modules.scoreboard.ScoreboardModule;
|
||||||
import net.viper.status.modules.antibot.AntiBotModule;
|
import net.viper.status.modules.antibot.AntiBotModule;
|
||||||
import net.viper.status.modules.network.NetworkInfoModule;
|
import net.viper.status.modules.network.NetworkInfoModule;
|
||||||
import net.viper.status.modules.AutoMessage.AutoMessageModule;
|
import net.viper.status.modules.AutoMessage.AutoMessageModule;
|
||||||
@@ -111,6 +112,7 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
moduleManager.registerModule(new ServerSwitcherModule());
|
moduleManager.registerModule(new ServerSwitcherModule());
|
||||||
moduleManager.registerModule(new EconomyModule());
|
moduleManager.registerModule(new EconomyModule());
|
||||||
moduleManager.registerModule(new TablistModule());
|
moduleManager.registerModule(new TablistModule());
|
||||||
|
moduleManager.registerModule(new ScoreboardModule());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> forumBridge = Class.forName("net.viper.status.modules.forum.ForumBridgeModule");
|
Class<?> forumBridge = Class.forName("net.viper.status.modules.forum.ForumBridgeModule");
|
||||||
@@ -122,6 +124,20 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
|
|
||||||
moduleManager.enableAll(this);
|
moduleManager.enableAll(this);
|
||||||
|
|
||||||
|
// FIX: ScoreboardModule mit NetworkInfoModule verbinden (TPS-Fallback)
|
||||||
|
try {
|
||||||
|
net.viper.status.modules.scoreboard.ScoreboardModule sbMod =
|
||||||
|
(net.viper.status.modules.scoreboard.ScoreboardModule) moduleManager.getModule("ScoreboardModule");
|
||||||
|
net.viper.status.modules.network.NetworkInfoModule nimMod =
|
||||||
|
(net.viper.status.modules.network.NetworkInfoModule) moduleManager.getModule("NetworkInfoModule");
|
||||||
|
if (sbMod != null && nimMod != null) {
|
||||||
|
sbMod.setNetworkInfoModule(nimMod);
|
||||||
|
getLogger().info("[StatusAPI] ScoreboardModule → NetworkInfoModule TPS-Fallback verbunden.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().warning("[StatusAPI] TPS-Fallback konnte nicht verbunden werden: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// WebServer starten
|
// WebServer starten
|
||||||
shuttingDown = false;
|
shuttingDown = false;
|
||||||
requestExecutor = Executors.newFixedThreadPool(4, r -> {
|
requestExecutor = Executors.newFixedThreadPool(4, r -> {
|
||||||
@@ -528,6 +544,8 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
playerMap.put("last_seen", ps.lastSeen);
|
playerMap.put("last_seen", ps.lastSeen);
|
||||||
playerMap.put("playtime", ps.getPlaytimeWithCurrentSession());
|
playerMap.put("playtime", ps.getPlaytimeWithCurrentSession());
|
||||||
playerMap.put("joins", ps.joins);
|
playerMap.put("joins", ps.joins);
|
||||||
|
playerMap.put("kills", ps.kills);
|
||||||
|
playerMap.put("deaths", ps.deaths);
|
||||||
playerMap.put("online", ProxyServer.getInstance().getPlayer(ps.uuid) != null);
|
playerMap.put("online", ProxyServer.getInstance().getPlayer(ps.uuid) != null);
|
||||||
// Balance direkt aus MySQL (serverübergreifend)
|
// Balance direkt aus MySQL (serverübergreifend)
|
||||||
EconomyModule ecoModPlayer = (EconomyModule) moduleManager.getModule("EconomyModule");
|
EconomyModule ecoModPlayer = (EconomyModule) moduleManager.getModule("EconomyModule");
|
||||||
@@ -683,6 +701,79 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /stats/update – Kills und Deaths eines Spielers aktualisieren (von Backend-Server via StatusAPIBridge)
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/stats/update".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
StatsModule statsModUpd = (StatsModule) moduleManager.getModule("StatsModule");
|
||||||
|
if (statsModUpd == null) {
|
||||||
|
sendHttpResponse(out, "{\"success\":false,\"error\":\"stats_module_unavailable\"}", 503);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
String nameStr = extractJsonString(body, "name");
|
||||||
|
PlayerStats psUpd = resolvePlayer(uuidStr, nameStr, statsModUpd);
|
||||||
|
if (psUpd == null) {
|
||||||
|
// Spieler noch nicht bekannt → ignorieren (er hat sich noch nicht eingeloggt)
|
||||||
|
sendHttpResponse(out, "{\"success\":false,\"error\":\"player_not_found\"}", 404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String killsStr = extractJsonString(body, "kills");
|
||||||
|
String deathsStr = extractJsonString(body, "deaths");
|
||||||
|
synchronized (psUpd) {
|
||||||
|
try { if (killsStr != null && !killsStr.isEmpty()) psUpd.kills = Integer.parseInt(killsStr.trim()); } catch (Exception ignored) {}
|
||||||
|
try { if (deathsStr != null && !deathsStr.isEmpty()) psUpd.deaths = Integer.parseInt(deathsStr.trim()); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sendHttpResponse(out, "{\"success\":true}", 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /scoreboard/health – Leben eines Spielers aktualisieren (von StatusAPIBridge)
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/scoreboard/health".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
String healthStr = extractJsonString(body, "health");
|
||||||
|
if (uuidStr != null && !uuidStr.isEmpty() && healthStr != null && !healthStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
UUID hUuid = UUID.fromString(uuidStr.trim());
|
||||||
|
double health = Double.parseDouble(healthStr.trim());
|
||||||
|
net.viper.status.modules.scoreboard.ScoreboardModule.playerHealth.put(hUuid, health);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sendHttpResponse(out, "{\"success\":true}", 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /scoreboard/compass – Himmelsrichtung eines Spielers (von StatusAPIBridge)
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/scoreboard/compass".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
String compassStr = extractJsonString(body, "compass");
|
||||||
|
if (uuidStr != null && !uuidStr.isEmpty() && compassStr != null && !compassStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
UUID cUuid = UUID.fromString(uuidStr.trim());
|
||||||
|
net.viper.status.modules.scoreboard.ScoreboardModule.playerCompass.put(cUuid, compassStr.trim());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sendHttpResponse(out, "{\"success\":true}", 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /scoreboard/tps – TPS des Spieler-Servers (von StatusAPIBridge)
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/scoreboard/tps".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
String tpsStr = extractJsonString(body, "tps");
|
||||||
|
if (uuidStr != null && !uuidStr.isEmpty() && tpsStr != null && !tpsStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
UUID tUuid = UUID.fromString(uuidStr.trim());
|
||||||
|
double tps = Double.parseDouble(tpsStr.trim());
|
||||||
|
net.viper.status.modules.scoreboard.ScoreboardModule.playerTps.put(tUuid, tps);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sendHttpResponse(out, "{\"success\":true}", 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// POST /player/world – Welt eines Spielers aktualisieren (von StatusAPIBridge)
|
// POST /player/world – Welt eines Spielers aktualisieren (von StatusAPIBridge)
|
||||||
if ("POST".equalsIgnoreCase(method) && "/player/world".equalsIgnoreCase(pathOnly)) {
|
if ("POST".equalsIgnoreCase(method) && "/player/world".equalsIgnoreCase(pathOnly)) {
|
||||||
String body = readBody(in, headers);
|
String body = readBody(in, headers);
|
||||||
@@ -697,6 +788,35 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /player/data – Koordinaten, Gamemode, Exp, Food, Speed
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/player/data".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
if (uuidStr != null && !uuidStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
UUID uid = UUID.fromString(uuidStr.trim());
|
||||||
|
String xS = extractJsonString(body, "x");
|
||||||
|
String yS = extractJsonString(body, "y");
|
||||||
|
String zS = extractJsonString(body, "z");
|
||||||
|
String gm = extractJsonString(body, "gamemode");
|
||||||
|
String expS= extractJsonString(body, "exp");
|
||||||
|
String fdS = extractJsonString(body, "food");
|
||||||
|
String spS = extractJsonString(body, "speed");
|
||||||
|
String wld = extractJsonString(body, "world");
|
||||||
|
if (xS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerX.put(uid, (int)Double.parseDouble(xS));
|
||||||
|
if (yS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerY.put(uid, (int)Double.parseDouble(yS));
|
||||||
|
if (zS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerZ.put(uid, (int)Double.parseDouble(zS));
|
||||||
|
if (gm != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerGamemode.put(uid, gm);
|
||||||
|
if (expS!= null) net.viper.status.modules.scoreboard.ScoreboardModule.playerExp.put(uid, (int)Double.parseDouble(expS));
|
||||||
|
if (fdS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerFood.put(uid, (int)Double.parseDouble(fdS));
|
||||||
|
if (spS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerSpeed.put(uid, Double.parseDouble(spS));
|
||||||
|
if (wld != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerWorld.put(uid, wld);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sendHttpResponse(out, "{\"success\":true}", 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// GET – Status-Endpunkt
|
// GET – Status-Endpunkt
|
||||||
if (inputLine.startsWith("GET")) {
|
if (inputLine.startsWith("GET")) {
|
||||||
Map<String, Object> data = new LinkedHashMap<>();
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
@@ -775,6 +895,8 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
if (ps != null) {
|
if (ps != null) {
|
||||||
playerInfo.put("playtime", ps.getPlaytimeWithCurrentSession());
|
playerInfo.put("playtime", ps.getPlaytimeWithCurrentSession());
|
||||||
playerInfo.put("joins", ps.joins);
|
playerInfo.put("joins", ps.joins);
|
||||||
|
playerInfo.put("kills", ps.kills);
|
||||||
|
playerInfo.put("deaths", ps.deaths);
|
||||||
playerInfo.put("first_seen", ps.firstSeen);
|
playerInfo.put("first_seen", ps.firstSeen);
|
||||||
playerInfo.put("last_seen", ps.lastSeen);
|
playerInfo.put("last_seen", ps.lastSeen);
|
||||||
// Balance direkt aus MySQL (serverübergreifend)
|
// Balance direkt aus MySQL (serverübergreifend)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class AutoMessageModule implements Module {
|
|||||||
public void onEnable(Plugin plugin) {
|
public void onEnable(Plugin plugin) {
|
||||||
this.api = (StatusAPI) plugin;
|
this.api = (StatusAPI) plugin;
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
ensureMessagesFileExists();
|
||||||
|
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
|
|
||||||
@@ -59,6 +60,36 @@ public class AutoMessageModule implements Module {
|
|||||||
cancelTask();
|
cancelTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureMessagesFileExists() {
|
||||||
|
File dataFolder = api.getDataFolder();
|
||||||
|
if (!dataFolder.exists()) dataFolder.mkdirs();
|
||||||
|
|
||||||
|
File target = new File(dataFolder, fileName);
|
||||||
|
if (target.exists()) return;
|
||||||
|
|
||||||
|
// Datei aus den Plugin-Ressourcen kopieren
|
||||||
|
try (java.io.InputStream in = api.getResourceAsStream(fileName)) {
|
||||||
|
if (in != null) {
|
||||||
|
Files.copy(in, target.toPath());
|
||||||
|
api.getLogger().info("[AutoMessage] " + fileName + " wurde aus den Ressourcen erstellt.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
api.getLogger().warning("[AutoMessage] Konnte " + fileName + " nicht aus Ressourcen kopieren: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: leere Datei mit Hinweis anlegen
|
||||||
|
try {
|
||||||
|
Files.write(target.toPath(),
|
||||||
|
("# AutoMessage – eine Nachricht pro Zeile\n" +
|
||||||
|
"# Farben mit & oder §-Codes, z.B. &aGrüner Text\n" +
|
||||||
|
"# Kommentarzeilen (# ...) und Leerzeilen werden ignoriert\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
api.getLogger().info("[AutoMessage] " + fileName + " wurde als leere Vorlage erstellt.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
api.getLogger().severe("[AutoMessage] Konnte " + fileName + " nicht erstellen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadSettings() {
|
private void loadSettings() {
|
||||||
Properties props = api.getVerifyProperties();
|
Properties props = api.getVerifyProperties();
|
||||||
enabled = Boolean.parseBoolean(props.getProperty("automessage.enabled", "false"));
|
enabled = Boolean.parseBoolean(props.getProperty("automessage.enabled", "false"));
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ public class NetworkInfoModule implements Module {
|
|||||||
private long lastPlayerAlertAt = 0L;
|
private long lastPlayerAlertAt = 0L;
|
||||||
private long lastTpsAlertAt = 0L;
|
private long lastTpsAlertAt = 0L;
|
||||||
private volatile double currentProxyTps = 20.0D;
|
private volatile double currentProxyTps = 20.0D;
|
||||||
|
|
||||||
|
/** FIX: Öffentlicher Getter damit ScoreboardModule als TPS-Fallback darauf zugreifen kann */
|
||||||
|
public double getProxyTps() { return currentProxyTps; }
|
||||||
private long lastTpsSampleAtMs = 0L;
|
private long lastTpsSampleAtMs = 0L;
|
||||||
private ScheduledTask alertTask;
|
private ScheduledTask alertTask;
|
||||||
private ScheduledTask tpsSamplerTask;
|
private ScheduledTask tpsSamplerTask;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,7 @@ public class ServerSwitcherModule implements Module {
|
|||||||
private List<String> aliases = new ArrayList<>(Arrays.asList("wechsel", "switch"));
|
private List<String> aliases = new ArrayList<>(Arrays.asList("wechsel", "switch"));
|
||||||
private List<String> serverWhitelist = new ArrayList<>();
|
private List<String> serverWhitelist = new ArrayList<>();
|
||||||
|
|
||||||
private String colorHeader = "&8&m---&r &6&lServer-Menue &8&m---";
|
private String colorHeader = "&8&m---&r &6&lServer-Menü &8&m---";
|
||||||
private String colorEntry = "&7>> &e";
|
private String colorEntry = "&7>> &e";
|
||||||
private String colorOnline = "&a";
|
private String colorOnline = "&a";
|
||||||
private String colorOffline = "&c";
|
private String colorOffline = "&c";
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ public class PlayerStats {
|
|||||||
public long currentSessionStart;
|
public long currentSessionStart;
|
||||||
public int joins;
|
public int joins;
|
||||||
|
|
||||||
|
// Combat
|
||||||
|
public int kills;
|
||||||
|
public int deaths;
|
||||||
|
|
||||||
// Economy
|
// Economy
|
||||||
public double balance;
|
public double balance;
|
||||||
public double totalEarned;
|
public double totalEarned;
|
||||||
@@ -34,6 +38,8 @@ public class PlayerStats {
|
|||||||
this.totalPlaytime = 0;
|
this.totalPlaytime = 0;
|
||||||
this.currentSessionStart = 0;
|
this.currentSessionStart = 0;
|
||||||
this.joins = 0;
|
this.joins = 0;
|
||||||
|
this.kills = 0;
|
||||||
|
this.deaths = 0;
|
||||||
this.balance = 0.0;
|
this.balance = 0.0;
|
||||||
this.totalEarned = 0.0;
|
this.totalEarned = 0.0;
|
||||||
this.totalSpent = 0.0;
|
this.totalSpent = 0.0;
|
||||||
@@ -70,11 +76,35 @@ public class PlayerStats {
|
|||||||
return totalPlaytime;
|
return totalPlaytime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format: uuid|name|firstSeen|lastSeen|totalPlaytime|currentSessionStart|joins|
|
||||||
|
* kills|deaths|
|
||||||
|
* balance|totalEarned|totalSpent|transactionsCount|
|
||||||
|
* bansCount|mutesCount|warnsCount|lastPunishmentAt|lastPunishmentType|punishmentScore
|
||||||
|
*
|
||||||
|
* HINWEIS: kills/deaths wurden in Version 1.17.1 als Felder 7 und 8 eingefügt.
|
||||||
|
* fromLine() ist rückwärtskompatibel (alte Dateien ohne kills/deaths werden 0 gesetzt).
|
||||||
|
*/
|
||||||
public synchronized String toLine() {
|
public synchronized String toLine() {
|
||||||
String safeType = (lastPunishmentType == null ? "" : lastPunishmentType).replace("|", "_");
|
String safeType = (lastPunishmentType == null ? "" : lastPunishmentType).replace("|", "_");
|
||||||
return uuid + "|" + name.replace("|", "_") + "|" + firstSeen + "|" + lastSeen + "|" + totalPlaytime + "|" + currentSessionStart + "|" + joins
|
return uuid + "|" + name.replace("|", "_")
|
||||||
+ "|" + balance + "|" + totalEarned + "|" + totalSpent + "|" + transactionsCount
|
+ "|" + firstSeen
|
||||||
+ "|" + bansCount + "|" + mutesCount + "|" + warnsCount + "|" + lastPunishmentAt + "|" + safeType + "|" + punishmentScore;
|
+ "|" + lastSeen
|
||||||
|
+ "|" + totalPlaytime
|
||||||
|
+ "|" + currentSessionStart
|
||||||
|
+ "|" + joins
|
||||||
|
+ "|" + kills
|
||||||
|
+ "|" + deaths
|
||||||
|
+ "|" + balance
|
||||||
|
+ "|" + totalEarned
|
||||||
|
+ "|" + totalSpent
|
||||||
|
+ "|" + transactionsCount
|
||||||
|
+ "|" + bansCount
|
||||||
|
+ "|" + mutesCount
|
||||||
|
+ "|" + warnsCount
|
||||||
|
+ "|" + lastPunishmentAt
|
||||||
|
+ "|" + safeType
|
||||||
|
+ "|" + punishmentScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlayerStats fromLine(String line) {
|
public static PlayerStats fromLine(String line) {
|
||||||
@@ -89,14 +119,50 @@ public class PlayerStats {
|
|||||||
ps.totalPlaytime = Long.parseLong(parts[4]);
|
ps.totalPlaytime = Long.parseLong(parts[4]);
|
||||||
ps.currentSessionStart = Long.parseLong(parts[5]);
|
ps.currentSessionStart = Long.parseLong(parts[5]);
|
||||||
ps.joins = Integer.parseInt(parts[6]);
|
ps.joins = Integer.parseInt(parts[6]);
|
||||||
// Economy (felder 7-10)
|
|
||||||
|
// Erkennung ob altes Format (ohne kills/deaths, Economy ab Index 7)
|
||||||
|
// oder neues Format (kills/deaths ab Index 7, Economy ab Index 9).
|
||||||
|
// Altes Format hat 17 Felder (Index 0-16), neues hat 19 (Index 0-18).
|
||||||
|
// Heuristik: Wenn parts[7] ein gültiger Integer ist UND parts[9] wie eine
|
||||||
|
// Gleitkommazahl aussieht → neues Format. Sonst altes Format.
|
||||||
|
boolean newFormat = false;
|
||||||
|
if (parts.length >= 19) {
|
||||||
|
try {
|
||||||
|
Integer.parseInt(parts[7]); // kills
|
||||||
|
Integer.parseInt(parts[8]); // deaths
|
||||||
|
Double.parseDouble(parts[9]); // balance (Gleitkomma)
|
||||||
|
newFormat = true;
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFormat) {
|
||||||
|
// Neues Format: kills ab [7], deaths ab [8], economy ab [9]
|
||||||
|
try { ps.kills = Integer.parseInt(parts[7]); } catch (Exception ignored) {}
|
||||||
|
try { ps.deaths = Integer.parseInt(parts[8]); } catch (Exception ignored) {}
|
||||||
|
if (parts.length >= 13) {
|
||||||
|
try { ps.balance = Double.parseDouble(parts[9]); } catch (Exception ignored) {}
|
||||||
|
try { ps.totalEarned = Double.parseDouble(parts[10]); } catch (Exception ignored) {}
|
||||||
|
try { ps.totalSpent = Double.parseDouble(parts[11]); } catch (Exception ignored) {}
|
||||||
|
try { ps.transactionsCount = Integer.parseInt(parts[12]); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
if (parts.length >= 19) {
|
||||||
|
try { ps.bansCount = Integer.parseInt(parts[13]); } catch (Exception ignored) {}
|
||||||
|
try { ps.mutesCount = Integer.parseInt(parts[14]); } catch (Exception ignored) {}
|
||||||
|
try { ps.warnsCount = Integer.parseInt(parts[15]); } catch (Exception ignored) {}
|
||||||
|
try { ps.lastPunishmentAt = Long.parseLong(parts[16]); } catch (Exception ignored) {}
|
||||||
|
ps.lastPunishmentType = parts[17];
|
||||||
|
try { ps.punishmentScore = Integer.parseInt(parts[18]); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Altes Format (kompatibel): kills/deaths = 0, Economy ab [7]
|
||||||
|
ps.kills = 0;
|
||||||
|
ps.deaths = 0;
|
||||||
if (parts.length >= 11) {
|
if (parts.length >= 11) {
|
||||||
try { ps.balance = Double.parseDouble(parts[7]); } catch (Exception ignored) {}
|
try { ps.balance = Double.parseDouble(parts[7]); } catch (Exception ignored) {}
|
||||||
try { ps.totalEarned = Double.parseDouble(parts[8]); } catch (Exception ignored) {}
|
try { ps.totalEarned = Double.parseDouble(parts[8]); } catch (Exception ignored) {}
|
||||||
try { ps.totalSpent = Double.parseDouble(parts[9]); } catch (Exception ignored) {}
|
try { ps.totalSpent = Double.parseDouble(parts[9]); } catch (Exception ignored) {}
|
||||||
try { ps.transactionsCount = Integer.parseInt(parts[10]); } catch (Exception ignored) {}
|
try { ps.transactionsCount = Integer.parseInt(parts[10]); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
// Punishments (felder 11-16)
|
|
||||||
if (parts.length >= 17) {
|
if (parts.length >= 17) {
|
||||||
try { ps.bansCount = Integer.parseInt(parts[11]); } catch (Exception ignored) {}
|
try { ps.bansCount = Integer.parseInt(parts[11]); } catch (Exception ignored) {}
|
||||||
try { ps.mutesCount = Integer.parseInt(parts[12]); } catch (Exception ignored) {}
|
try { ps.mutesCount = Integer.parseInt(parts[12]); } catch (Exception ignored) {}
|
||||||
@@ -105,6 +171,7 @@ public class PlayerStats {
|
|||||||
ps.lastPunishmentType = parts[15];
|
ps.lastPunishmentType = parts[15];
|
||||||
try { ps.punishmentScore = Integer.parseInt(parts[16]); } catch (Exception ignored) {}
|
try { ps.punishmentScore = Integer.parseInt(parts[16]); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ps;
|
return ps;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -10,11 +10,20 @@ import net.viper.status.module.Module;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StatsModule: Kümmert sich eigenständig um das Tracking der Spielerdaten.
|
* StatsModule: Tracking von Spielerdaten (Playtime, Joins, Kills, Deaths).
|
||||||
* Implementiert Module (für das Lifecycle) und Listener (für die Events).
|
*
|
||||||
|
* Fixes:
|
||||||
|
* - BUG-1: Crash-Recovery für currentSessionStart (verhindert falsche Spielzeit nach Absturz)
|
||||||
|
* - BUG-2: kills / deaths werden jetzt getrackt und per POST /stats/update aktualisiert
|
||||||
*/
|
*/
|
||||||
public class StatsModule implements Module, Listener {
|
public class StatsModule implements Module, Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximale Sessionlänge nach einem Crash noch gutschreiben (24 Stunden).
|
||||||
|
* Längere Differenzen sind unrealistisch → werden ignoriert, currentSessionStart = 0 gesetzt.
|
||||||
|
*/
|
||||||
|
private static final long MAX_SESSION_SECONDS = 86_400L;
|
||||||
|
|
||||||
private StatsManager manager;
|
private StatsManager manager;
|
||||||
private StatsStorage storage;
|
private StatsStorage storage;
|
||||||
|
|
||||||
@@ -25,21 +34,64 @@ public class StatsModule implements Module, Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable(Plugin plugin) {
|
public void onEnable(Plugin plugin) {
|
||||||
// Initialisierung
|
|
||||||
manager = new StatsManager();
|
manager = new StatsManager();
|
||||||
storage = new StatsStorage(plugin.getDataFolder());
|
storage = new StatsStorage(plugin.getDataFolder());
|
||||||
|
|
||||||
// Laden
|
|
||||||
try {
|
try {
|
||||||
storage.load(manager);
|
storage.load(manager);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().warning("Fehler beim Laden der Stats: " + e.getMessage());
|
plugin.getLogger().warning("Fehler beim Laden der Stats: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event Listener registrieren
|
// -----------------------------------------------------------------------
|
||||||
|
// FIX BUG-1: Crash-Recovery – offene Sessions bereinigen.
|
||||||
|
//
|
||||||
|
// Bei normalem Shutdown setzt onDisable() currentSessionStart = 0 und speichert.
|
||||||
|
// Bei einem Crash (kill -9, OOM, etc.) passiert das nicht. Beim nächsten Start
|
||||||
|
// sind alle Spieler offline, aber currentSessionStart enthält noch den alten
|
||||||
|
// Timestamp. getPlaytimeWithCurrentSession() würde dann fälschlicherweise
|
||||||
|
// (now - alter_crash_timestamp) zur Spielzeit addieren → massiv falscher Wert.
|
||||||
|
//
|
||||||
|
// Fix: Nach dem Laden jeden Eintrag prüfen. Falls currentSessionStart > 0:
|
||||||
|
// - Plausible Differenz (≤ MAX_SESSION_SECONDS) → als echte Zeit gutschreiben
|
||||||
|
// - Unplausibel (> MAX_SESSION_SECONDS) → verwerfen, nur zurücksetzen
|
||||||
|
// - In beiden Fällen: currentSessionStart = 0 setzen
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
long now = System.currentTimeMillis() / 1000L;
|
||||||
|
int recovered = 0;
|
||||||
|
for (PlayerStats ps : manager.all()) {
|
||||||
|
synchronized (ps) {
|
||||||
|
if (ps.currentSessionStart > 0) {
|
||||||
|
long delta = now - ps.currentSessionStart;
|
||||||
|
if (delta > 0 && delta <= MAX_SESSION_SECONDS) {
|
||||||
|
ps.totalPlaytime += delta;
|
||||||
|
recovered++;
|
||||||
|
} else if (delta > MAX_SESSION_SECONDS) {
|
||||||
|
plugin.getLogger().warning(
|
||||||
|
"[StatsModule] Unplausibler currentSessionStart für " + ps.name
|
||||||
|
+ " (delta=" + delta + "s > " + MAX_SESSION_SECONDS + "s). "
|
||||||
|
+ "Session wird ohne Gutschrift zurückgesetzt."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ps.currentSessionStart = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recovered > 0) {
|
||||||
|
plugin.getLogger().info(
|
||||||
|
"[StatsModule] Crash-Recovery: " + recovered + " offene Session(en) bereinigt und gespeichert."
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
storage.save(manager);
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("Fehler beim Speichern nach Crash-Recovery: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||||
|
|
||||||
// Auto-Save Task (alle 5 Minuten)
|
// Auto-Save alle 5 Minuten
|
||||||
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
||||||
try {
|
try {
|
||||||
storage.save(manager);
|
storage.save(manager);
|
||||||
@@ -52,16 +104,13 @@ public class StatsModule implements Module, Listener {
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable(Plugin plugin) {
|
public void onDisable(Plugin plugin) {
|
||||||
if (manager != null && storage != null) {
|
if (manager != null && storage != null) {
|
||||||
// Laufende Sessions beenden vor dem Speichern
|
|
||||||
long now = System.currentTimeMillis() / 1000L;
|
long now = System.currentTimeMillis() / 1000L;
|
||||||
for (PlayerStats ps : manager.all()) {
|
for (PlayerStats ps : manager.all()) {
|
||||||
synchronized (ps) {
|
synchronized (ps) {
|
||||||
if (ps.currentSessionStart > 0) {
|
if (ps.currentSessionStart > 0) {
|
||||||
long delta = now - ps.currentSessionStart;
|
long delta = now - ps.currentSessionStart;
|
||||||
if (delta > 0) {
|
if (delta > 0) ps.totalPlaytime += delta;
|
||||||
ps.totalPlaytime += delta;
|
ps.currentSessionStart = 0;
|
||||||
}
|
|
||||||
ps.currentSessionStart = 0; // Session beenden
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +122,6 @@ public class StatsModule implements Module, Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Öffentlicher Zugriff für den WebServer
|
|
||||||
public StatsManager getManager() {
|
public StatsManager getManager() {
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ softdepend:
|
|||||||
- Geyser-BungeeCord
|
- Geyser-BungeeCord
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
|
# ── ScoreboardModule ──────────────────────────────────────
|
||||||
|
scoreboard:
|
||||||
|
description: Scoreboard ein-/ausblenden oder zwischen Player/Admin wechseln
|
||||||
|
usage: /scoreboard [hide|show|player|admin]
|
||||||
|
aliases: [sb, togglesb]
|
||||||
|
|
||||||
# ── Economy ───────────────────────────────────────────────
|
# ── Economy ───────────────────────────────────────────────
|
||||||
pay:
|
pay:
|
||||||
description: Überweise Geld an einen Spieler (auch offline)
|
description: Überweise Geld an einen Spieler (auch offline)
|
||||||
|
|||||||
115
StatusAPI/src/main/resources/scoreboard.properties
Normal file
115
StatusAPI/src/main/resources/scoreboard.properties
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# ScoreboardModule Konfiguration
|
||||||
|
# Platzhalter Spieler: %player% %rank% %money% %server% %compass% %health% %hearts% %date%
|
||||||
|
# %ping% %online% %maxplayers% %time% %playtime% %news%
|
||||||
|
# %x% %y% %z% %world% %gamemode% %exp% %food% %foodsym% %speed%
|
||||||
|
# Platzhalter Admin: %tps% %ram% %proxymem% %uptime% %servers%
|
||||||
|
# Gradient: %gradient:FARBE1:FARBE2:TEXT% (beliebig viele Farb-Stopps)
|
||||||
|
# Sonstiges: %line%
|
||||||
|
# Farben: &-Codes und Hex &#FF6600
|
||||||
|
|
||||||
|
scoreboard.enabled=true
|
||||||
|
# Update-Intervall in Millisekunden - MINIMUM 250! (500 = 0.5s empfohlen)
|
||||||
|
scoreboard.update_interval=500
|
||||||
|
scoreboard.title=&lViper Network
|
||||||
|
scoreboard.admin_title=&l[Admin] Panel
|
||||||
|
|
||||||
|
# Laufschrift – leer lassen zum Deaktivieren
|
||||||
|
scoreboard.ticker.text=
|
||||||
|
scoreboard.ticker.width=26
|
||||||
|
scoreboard.ticker.speed=1
|
||||||
|
|
||||||
|
scoreboard.rainbow.enabled=true
|
||||||
|
# wave = fließende Farbwelle | chars = Regenbogen pro Buchstabe | line = eine Farbe
|
||||||
|
scoreboard.rainbow.mode=wave
|
||||||
|
# Wellengeschwindigkeit: 1=sehr langsam, 10=normal, 50=schnell, 100=sehr schnell
|
||||||
|
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.admin_permission=statusapi.scoreboard.admin
|
||||||
|
|
||||||
|
scoreboard.time_format=HH:mm
|
||||||
|
scoreboard.date_format=dd.MM.yyyy
|
||||||
|
scoreboard.timezone=Europe/Berlin
|
||||||
|
scoreboard.money_format=#,##0.00
|
||||||
|
scoreboard.money_decimal_separator=,
|
||||||
|
|
||||||
|
# ===================================================
|
||||||
|
# SEPARATOR – wird als %line% Placeholder genutzt
|
||||||
|
# Wähle einen Stil oder erstelle deinen eigenen:
|
||||||
|
#
|
||||||
|
# scoreboard.separator=&8&m-------------------- (Standard)
|
||||||
|
# scoreboard.separator=&8&m==================== (Doppelt)
|
||||||
|
# scoreboard.separator=&8&m~~~~~~~~~~~~~~~~~~~~ (Wellig)
|
||||||
|
# scoreboard.separator=&8&m.................... (Punkte)
|
||||||
|
# scoreboard.separator=&8&m──────────────────── (Dünn)
|
||||||
|
# scoreboard.separator=&8&m════════════════════ (Dick)
|
||||||
|
# scoreboard.separator=&8◆◇◆◇◆◇◆◇◆◇◆◇◆◇◆◇◆◇◆◇ (Diamanten)
|
||||||
|
# scoreboard.separator=%gradient:&8:&7:────────────────────% (Gradient)
|
||||||
|
# scoreboard.separator=%gradient:#FF0000:#0000FF:────────────────────% (Farbig)
|
||||||
|
# scoreboard.separator= (Leer/unsichtbar)
|
||||||
|
# ===================================================
|
||||||
|
scoreboard.separator=&8&m--------------------
|
||||||
|
|
||||||
|
# ===================================================
|
||||||
|
# NEWS-TICKER – erscheint als %news% Placeholder
|
||||||
|
# Leer lassen zum Deaktivieren
|
||||||
|
# ===================================================
|
||||||
|
scoreboard.news.text=&eWillkommen auf Viper Network!
|
||||||
|
scoreboard.news.prefix=&8[&6News&8] &r
|
||||||
|
scoreboard.news.width=26
|
||||||
|
# Geschwindigkeit: 1=langsam, 2=normal, 3=schnell
|
||||||
|
scoreboard.news.speed=1
|
||||||
|
|
||||||
|
# Sekunden pro Rotation (0 = kein Wechsel)
|
||||||
|
scoreboard.rotation_interval=4
|
||||||
|
|
||||||
|
# ===================================================
|
||||||
|
# ZEILEN – max 15 sichtbar
|
||||||
|
# Rotation pro Zeile:
|
||||||
|
# scoreboard.lines.N = Variante 1 (immer sichtbar / nur Variante)
|
||||||
|
# scoreboard.lines.N.2 = Variante 2 (wechselt alle rotation_interval Sekunden)
|
||||||
|
# scoreboard.lines.N.3 = Variante 3 usw.
|
||||||
|
# Gradient: %gradient:FARBE1:FARBE2:TEXT%
|
||||||
|
# ===================================================
|
||||||
|
scoreboard.lines.1=%line%
|
||||||
|
scoreboard.lines.2=%gradient:&b:&f:&b:&l> Player Info:%
|
||||||
|
scoreboard.lines.3=&7%rank% &f%player%
|
||||||
|
scoreboard.lines.4=
|
||||||
|
scoreboard.lines.5=&7Spielzeit: &f%playtime%
|
||||||
|
scoreboard.lines.5.2=&7Leben: &c%health%
|
||||||
|
scoreboard.lines.5.3=&7Hunger: B4513%foodsym%
|
||||||
|
scoreboard.lines.6=
|
||||||
|
scoreboard.lines.7=%gradient:&b:&f:&b:&l> Money:%
|
||||||
|
scoreboard.lines.8=&a$%money%
|
||||||
|
scoreboard.lines.9=
|
||||||
|
scoreboard.lines.10=%gradient:&b:&f:&b:&l> Server Info:%
|
||||||
|
scoreboard.lines.11=&f%server%
|
||||||
|
scoreboard.lines.11.2=&7Ping: &f%ping%ms &8| &7Online: &f%online%
|
||||||
|
scoreboard.lines.12=
|
||||||
|
scoreboard.lines.13=%news%
|
||||||
|
scoreboard.lines.14=%line%
|
||||||
|
scoreboard.lines.15=&7%compass%
|
||||||
|
|
||||||
|
# ===================================================
|
||||||
|
# ADMIN-ZEILEN
|
||||||
|
# ===================================================
|
||||||
|
scoreboard.admin_lines.1=%line%
|
||||||
|
scoreboard.admin_lines.2=%gradient:&b:&f:&b:&l> Player Info:%
|
||||||
|
scoreboard.admin_lines.3=&7%rank% &f%player%
|
||||||
|
scoreboard.admin_lines.4=&7Gamemode: &f%gamemode%
|
||||||
|
scoreboard.admin_lines.5=&7Leben: &c%health%
|
||||||
|
scoreboard.admin_lines.5.2=&7Hunger: B4513%foodsym%
|
||||||
|
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=&7Spieler: &f%online% &8| &7%maxplayers%
|
||||||
|
scoreboard.admin_lines.13=%news%
|
||||||
|
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%
|
||||||
Reference in New Issue
Block a user