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.modules.economy.EconomyModule;
|
||||
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.network.NetworkInfoModule;
|
||||
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 EconomyModule());
|
||||
moduleManager.registerModule(new TablistModule());
|
||||
moduleManager.registerModule(new ScoreboardModule());
|
||||
|
||||
try {
|
||||
Class<?> forumBridge = Class.forName("net.viper.status.modules.forum.ForumBridgeModule");
|
||||
@@ -122,6 +124,20 @@ public class StatusAPI extends Plugin implements Runnable {
|
||||
|
||||
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
|
||||
shuttingDown = false;
|
||||
requestExecutor = Executors.newFixedThreadPool(4, r -> {
|
||||
@@ -528,6 +544,8 @@ public class StatusAPI extends Plugin implements Runnable {
|
||||
playerMap.put("last_seen", ps.lastSeen);
|
||||
playerMap.put("playtime", ps.getPlaytimeWithCurrentSession());
|
||||
playerMap.put("joins", ps.joins);
|
||||
playerMap.put("kills", ps.kills);
|
||||
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");
|
||||
@@ -683,6 +701,79 @@ public class StatusAPI extends Plugin implements Runnable {
|
||||
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)
|
||||
if ("POST".equalsIgnoreCase(method) && "/player/world".equalsIgnoreCase(pathOnly)) {
|
||||
String body = readBody(in, headers);
|
||||
@@ -697,6 +788,35 @@ public class StatusAPI extends Plugin implements Runnable {
|
||||
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
|
||||
if (inputLine.startsWith("GET")) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
@@ -775,6 +895,8 @@ public class StatusAPI extends Plugin implements Runnable {
|
||||
if (ps != null) {
|
||||
playerInfo.put("playtime", ps.getPlaytimeWithCurrentSession());
|
||||
playerInfo.put("joins", ps.joins);
|
||||
playerInfo.put("kills", ps.kills);
|
||||
playerInfo.put("deaths", ps.deaths);
|
||||
playerInfo.put("first_seen", ps.firstSeen);
|
||||
playerInfo.put("last_seen", ps.lastSeen);
|
||||
// Balance direkt aus MySQL (serverübergreifend)
|
||||
|
||||
@@ -47,6 +47,7 @@ public class AutoMessageModule implements Module {
|
||||
public void onEnable(Plugin plugin) {
|
||||
this.api = (StatusAPI) plugin;
|
||||
loadSettings();
|
||||
ensureMessagesFileExists();
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
@@ -59,6 +60,36 @@ public class AutoMessageModule implements Module {
|
||||
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() {
|
||||
Properties props = api.getVerifyProperties();
|
||||
enabled = Boolean.parseBoolean(props.getProperty("automessage.enabled", "false"));
|
||||
|
||||
@@ -65,6 +65,9 @@ public class NetworkInfoModule implements Module {
|
||||
private long lastPlayerAlertAt = 0L;
|
||||
private long lastTpsAlertAt = 0L;
|
||||
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 ScheduledTask alertTask;
|
||||
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> 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 colorOnline = "&a";
|
||||
private String colorOffline = "&c";
|
||||
|
||||
@@ -11,6 +11,10 @@ public class PlayerStats {
|
||||
public long currentSessionStart;
|
||||
public int joins;
|
||||
|
||||
// Combat
|
||||
public int kills;
|
||||
public int deaths;
|
||||
|
||||
// Economy
|
||||
public double balance;
|
||||
public double totalEarned;
|
||||
@@ -34,6 +38,8 @@ public class PlayerStats {
|
||||
this.totalPlaytime = 0;
|
||||
this.currentSessionStart = 0;
|
||||
this.joins = 0;
|
||||
this.kills = 0;
|
||||
this.deaths = 0;
|
||||
this.balance = 0.0;
|
||||
this.totalEarned = 0.0;
|
||||
this.totalSpent = 0.0;
|
||||
@@ -70,11 +76,35 @@ public class PlayerStats {
|
||||
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() {
|
||||
String safeType = (lastPunishmentType == null ? "" : lastPunishmentType).replace("|", "_");
|
||||
return uuid + "|" + name.replace("|", "_") + "|" + firstSeen + "|" + lastSeen + "|" + totalPlaytime + "|" + currentSessionStart + "|" + joins
|
||||
+ "|" + balance + "|" + totalEarned + "|" + totalSpent + "|" + transactionsCount
|
||||
+ "|" + bansCount + "|" + mutesCount + "|" + warnsCount + "|" + lastPunishmentAt + "|" + safeType + "|" + punishmentScore;
|
||||
return uuid + "|" + name.replace("|", "_")
|
||||
+ "|" + firstSeen
|
||||
+ "|" + lastSeen
|
||||
+ "|" + totalPlaytime
|
||||
+ "|" + currentSessionStart
|
||||
+ "|" + joins
|
||||
+ "|" + kills
|
||||
+ "|" + deaths
|
||||
+ "|" + balance
|
||||
+ "|" + totalEarned
|
||||
+ "|" + totalSpent
|
||||
+ "|" + transactionsCount
|
||||
+ "|" + bansCount
|
||||
+ "|" + mutesCount
|
||||
+ "|" + warnsCount
|
||||
+ "|" + lastPunishmentAt
|
||||
+ "|" + safeType
|
||||
+ "|" + punishmentScore;
|
||||
}
|
||||
|
||||
public static PlayerStats fromLine(String line) {
|
||||
@@ -84,26 +114,63 @@ public class PlayerStats {
|
||||
UUID uuid = UUID.fromString(parts[0]);
|
||||
String name = parts[1];
|
||||
PlayerStats ps = new PlayerStats(uuid, name);
|
||||
ps.firstSeen = Long.parseLong(parts[2]);
|
||||
ps.lastSeen = Long.parseLong(parts[3]);
|
||||
ps.totalPlaytime = Long.parseLong(parts[4]);
|
||||
ps.firstSeen = Long.parseLong(parts[2]);
|
||||
ps.lastSeen = Long.parseLong(parts[3]);
|
||||
ps.totalPlaytime = Long.parseLong(parts[4]);
|
||||
ps.currentSessionStart = Long.parseLong(parts[5]);
|
||||
ps.joins = Integer.parseInt(parts[6]);
|
||||
// Economy (felder 7-10)
|
||||
if (parts.length >= 11) {
|
||||
try { ps.balance = Double.parseDouble(parts[7]); } 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.transactionsCount = Integer.parseInt(parts[10]); } catch (Exception ignored) {}
|
||||
ps.joins = Integer.parseInt(parts[6]);
|
||||
|
||||
// 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) {}
|
||||
}
|
||||
// Punishments (felder 11-16)
|
||||
if (parts.length >= 17) {
|
||||
try { ps.bansCount = Integer.parseInt(parts[11]); } catch (Exception ignored) {}
|
||||
try { ps.mutesCount = Integer.parseInt(parts[12]); } catch (Exception ignored) {}
|
||||
try { ps.warnsCount = Integer.parseInt(parts[13]); } catch (Exception ignored) {}
|
||||
try { ps.lastPunishmentAt = Long.parseLong(parts[14]); } catch (Exception ignored) {}
|
||||
ps.lastPunishmentType = parts[15];
|
||||
try { ps.punishmentScore = Integer.parseInt(parts[16]); } 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) {
|
||||
try { ps.balance = Double.parseDouble(parts[7]); } 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.transactionsCount = Integer.parseInt(parts[10]); } catch (Exception ignored) {}
|
||||
}
|
||||
if (parts.length >= 17) {
|
||||
try { ps.bansCount = Integer.parseInt(parts[11]); } catch (Exception ignored) {}
|
||||
try { ps.mutesCount = Integer.parseInt(parts[12]); } catch (Exception ignored) {}
|
||||
try { ps.warnsCount = Integer.parseInt(parts[13]); } catch (Exception ignored) {}
|
||||
try { ps.lastPunishmentAt = Long.parseLong(parts[14]); } catch (Exception ignored) {}
|
||||
ps.lastPunishmentType = parts[15];
|
||||
try { ps.punishmentScore = Integer.parseInt(parts[16]); } catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
return ps;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -10,11 +10,20 @@ import net.viper.status.module.Module;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* StatsModule: Kümmert sich eigenständig um das Tracking der Spielerdaten.
|
||||
* Implementiert Module (für das Lifecycle) und Listener (für die Events).
|
||||
* StatsModule: Tracking von Spielerdaten (Playtime, Joins, Kills, Deaths).
|
||||
*
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* 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 StatsStorage storage;
|
||||
|
||||
@@ -25,21 +34,64 @@ public class StatsModule implements Module, Listener {
|
||||
|
||||
@Override
|
||||
public void onEnable(Plugin plugin) {
|
||||
// Initialisierung
|
||||
manager = new StatsManager();
|
||||
storage = new StatsStorage(plugin.getDataFolder());
|
||||
|
||||
// Laden
|
||||
try {
|
||||
storage.load(manager);
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
|
||||
// Auto-Save Task (alle 5 Minuten)
|
||||
// Auto-Save alle 5 Minuten
|
||||
plugin.getProxy().getScheduler().schedule(plugin, () -> {
|
||||
try {
|
||||
storage.save(manager);
|
||||
@@ -52,16 +104,13 @@ public class StatsModule implements Module, Listener {
|
||||
@Override
|
||||
public void onDisable(Plugin plugin) {
|
||||
if (manager != null && storage != null) {
|
||||
// Laufende Sessions beenden vor dem Speichern
|
||||
long now = System.currentTimeMillis() / 1000L;
|
||||
for (PlayerStats ps : manager.all()) {
|
||||
synchronized (ps) {
|
||||
if (ps.currentSessionStart > 0) {
|
||||
long delta = now - ps.currentSessionStart;
|
||||
if (delta > 0) {
|
||||
ps.totalPlaytime += delta;
|
||||
}
|
||||
ps.currentSessionStart = 0; // Session beenden
|
||||
if (delta > 0) ps.totalPlaytime += delta;
|
||||
ps.currentSessionStart = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +122,6 @@ public class StatsModule implements Module, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// Öffentlicher Zugriff für den WebServer
|
||||
public StatsManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@ softdepend:
|
||||
- Geyser-BungeeCord
|
||||
|
||||
commands:
|
||||
# ── ScoreboardModule ──────────────────────────────────────
|
||||
scoreboard:
|
||||
description: Scoreboard ein-/ausblenden oder zwischen Player/Admin wechseln
|
||||
usage: /scoreboard [hide|show|player|admin]
|
||||
aliases: [sb, togglesb]
|
||||
|
||||
# ── Economy ───────────────────────────────────────────────
|
||||
pay:
|
||||
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