Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627559356b | ||
|
|
5012bcd95b |
Binary file not shown.
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<groupId>net.viper.bungee</groupId>
|
<groupId>net.viper.bungee</groupId>
|
||||||
<artifactId>StatusAPI</artifactId>
|
<artifactId>StatusAPI</artifactId>
|
||||||
<version>4.1.0</version>
|
<version>4.1.1</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>StatusAPI</name>
|
<name>StatusAPI</name>
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
// Kontostand pro Spieler (UUID -> Balance), wird von StatusAPIBridge gepusht
|
// Kontostand pro Spieler (UUID -> Balance), wird von StatusAPIBridge gepusht
|
||||||
public static final ConcurrentHashMap<UUID, Double> playerBalances = new ConcurrentHashMap<>();
|
public static final ConcurrentHashMap<UUID, Double> playerBalances = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// PlaceholderAPI-Werte pro Spieler (UUID -> (placeholder -> aufgelöster Wert))
|
||||||
|
public static final ConcurrentHashMap<UUID, Map<String, String>> playerPapi = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/** Alle %token%-Tokens aus den Config-Dateien – als JSON-Array für GET /papi/tokens */
|
||||||
|
public static volatile String papiTokensJson = "[]";
|
||||||
|
|
||||||
// Debug-Modus (aus verify.properties)
|
// Debug-Modus (aus verify.properties)
|
||||||
public static boolean DEBUG = false;
|
public static boolean DEBUG = false;
|
||||||
|
|
||||||
@@ -124,6 +130,13 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
|
|
||||||
moduleManager.enableAll(this);
|
moduleManager.enableAll(this);
|
||||||
|
|
||||||
|
// PAPI-Tokens sofort scannen + nochmal nach 5s als Fallback (falls Configs erst beim Enable erstellt)
|
||||||
|
scanAndPublishPapiTokens();
|
||||||
|
ProxyServer.getInstance().getScheduler().schedule(this, this::scanAndPublishPapiTokens, 5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// /statusapi reload Befehl registrieren
|
||||||
|
ProxyServer.getInstance().getPluginManager().registerCommand(this, new StatusAPICommand(this));
|
||||||
|
|
||||||
// FIX: ScoreboardModule mit NetworkInfoModule verbinden (TPS-Fallback)
|
// FIX: ScoreboardModule mit NetworkInfoModule verbinden (TPS-Fallback)
|
||||||
try {
|
try {
|
||||||
net.viper.status.modules.scoreboard.ScoreboardModule sbMod =
|
net.viper.status.modules.scoreboard.ScoreboardModule sbMod =
|
||||||
@@ -836,6 +849,61 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /papi/tokens – liefert alle erkannten %token%-Placeholder als JSON-Array
|
||||||
|
if ("GET".equalsIgnoreCase(method) && "/papi/tokens".equalsIgnoreCase(pathOnly)) {
|
||||||
|
sendHttpResponse(out, papiTokensJson, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /player/papi – empfängt von StatusAPIBridge aufgelöste PAPI-Werte
|
||||||
|
if ("POST".equalsIgnoreCase(method) && "/player/papi".equalsIgnoreCase(pathOnly)) {
|
||||||
|
String body = readBody(in, headers);
|
||||||
|
String uuidStr = extractJsonString(body, "uuid");
|
||||||
|
if (uuidStr != null && !uuidStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
UUID papiUuid = UUID.fromString(uuidStr.trim());
|
||||||
|
Map<String, String> map = playerPapi.computeIfAbsent(papiUuid, k -> new ConcurrentHashMap<>());
|
||||||
|
// "placeholders"-Objekt manuell parsen
|
||||||
|
int start = body.indexOf("\"placeholders\"");
|
||||||
|
if (start >= 0) {
|
||||||
|
int brace = body.indexOf('{', start + 14);
|
||||||
|
if (brace >= 0) {
|
||||||
|
int i = brace + 1;
|
||||||
|
while (i < body.length()) {
|
||||||
|
while (i < body.length() && Character.isWhitespace(body.charAt(i))) i++;
|
||||||
|
if (i >= body.length() || body.charAt(i) == '}') break;
|
||||||
|
if (body.charAt(i) != '"') { i++; continue; }
|
||||||
|
i++;
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
while (i < body.length() && body.charAt(i) != '"') {
|
||||||
|
char ch = body.charAt(i++);
|
||||||
|
if (ch == '\\' && i < body.length()) i++; else key.append(ch);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
while (i < body.length() && (body.charAt(i) == ':' || Character.isWhitespace(body.charAt(i)))) i++;
|
||||||
|
if (i < body.length() && body.charAt(i) == '"') {
|
||||||
|
i++;
|
||||||
|
StringBuilder val = new StringBuilder();
|
||||||
|
boolean esc = false;
|
||||||
|
while (i < body.length()) {
|
||||||
|
char ch = body.charAt(i++);
|
||||||
|
if (esc) { val.append(ch == 'n' ? '\n' : ch == 't' ? '\t' : ch); esc = false; }
|
||||||
|
else if (ch == '\\') esc = true;
|
||||||
|
else if (ch == '"') break;
|
||||||
|
else val.append(ch);
|
||||||
|
}
|
||||||
|
if (key.length() > 0) map.put(key.toString(), val.toString());
|
||||||
|
}
|
||||||
|
while (i < body.length() && (body.charAt(i) == ',' || Character.isWhitespace(body.charAt(i)))) i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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<>();
|
||||||
@@ -1202,4 +1270,165 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
String e = event.trim().toLowerCase(Locale.ROOT);
|
String e = event.trim().toLowerCase(Locale.ROOT);
|
||||||
return e.contains("ip_rate") || e.contains("vpn") || e.contains("learning_threshold_block") || e.contains("block");
|
return e.contains("ip_rate") || e.contains("vpn") || e.contains("learning_threshold_block") || e.contains("block");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── PAPI-Token-Erkennung ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Alle Tokens die StatusAPI selbst auflöst – werden nicht an PAPI weitergegeben */
|
||||||
|
private static final Set<String> NATIVE_TOKENS = new HashSet<>(Arrays.asList(
|
||||||
|
"player", "rank", "money", "server", "compass", "health", "hearts", "ping",
|
||||||
|
"online", "maxplayers", "tps", "ram", "time", "playtime", "x", "y", "z",
|
||||||
|
"world", "gamemode", "exp", "food", "foodsym", "speed", "uptime", "servers",
|
||||||
|
"proxymem", "date", "news", "line", "balance",
|
||||||
|
"ticket_my_open", "ticket_open", "ticket_claimed",
|
||||||
|
"ticket_rating_good", "ticket_rating_bad", "ticket_rating_pct"
|
||||||
|
));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scannt alle .properties-Dateien im Plugin-Ordner nach %token%-Mustern,
|
||||||
|
* filtert nativ unterstützte Tokens heraus und veröffentlicht den Rest
|
||||||
|
* als JSON-Array unter GET /papi/tokens für StatusAPIBridge.
|
||||||
|
*/
|
||||||
|
public void scanAndPublishPapiTokens() {
|
||||||
|
Set<String> tokens = new LinkedHashSet<>();
|
||||||
|
File folder = getDataFolder();
|
||||||
|
if (folder.exists()) {
|
||||||
|
File[] files = folder.listFiles((dir, name) -> name.endsWith(".properties"));
|
||||||
|
if (files != null) {
|
||||||
|
for (File f : files) {
|
||||||
|
try (BufferedReader br = new BufferedReader(
|
||||||
|
new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
extractAllTokensFromText(line, tokens);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ressourcen-Defaults scannen (falls noch keine Dateien im Ordner)
|
||||||
|
for (String resource : new String[]{"scoreboard.properties"}) {
|
||||||
|
try (java.io.InputStream is = getResourceAsStream(resource)) {
|
||||||
|
if (is == null) continue;
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
extractAllTokensFromText(line, tokens);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
tokens.removeAll(NATIVE_TOKENS);
|
||||||
|
// JSON-Array bauen
|
||||||
|
StringBuilder json = new StringBuilder("[");
|
||||||
|
boolean first = true;
|
||||||
|
for (String token : tokens) {
|
||||||
|
if (!first) json.append(",");
|
||||||
|
json.append("\"").append(token.replace("\\", "\\\\").replace("\"", "\\\"")).append("\"");
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
json.append("]");
|
||||||
|
papiTokensJson = json.toString();
|
||||||
|
if (!tokens.isEmpty()) {
|
||||||
|
getLogger().info("[StatusAPI] " + tokens.size() + " PAPI-Token(s) erkannt: " + tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractAllTokensFromText(String text, Set<String> result) {
|
||||||
|
if (text == null || text.startsWith("#") || !text.contains("%")) return;
|
||||||
|
int eq = text.indexOf('=');
|
||||||
|
String value = eq >= 0 ? text.substring(eq + 1) : text;
|
||||||
|
int i = 0;
|
||||||
|
while (i < value.length()) {
|
||||||
|
int start = value.indexOf('%', i);
|
||||||
|
if (start < 0) break;
|
||||||
|
int end = value.indexOf('%', start + 1);
|
||||||
|
if (end < 0) break;
|
||||||
|
String token = value.substring(start + 1, end);
|
||||||
|
if (!token.isEmpty() && !token.contains(" ") && token.matches("[a-zA-Z0-9_:]+")) {
|
||||||
|
result.add(token);
|
||||||
|
}
|
||||||
|
i = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reload ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt Scoreboard und Tablist neu (Config + Tasks), ohne den HTTP-Server zu berühren.
|
||||||
|
* Alle anderen Module (Chat, AntiBot, etc.) bleiben unberührt.
|
||||||
|
*/
|
||||||
|
public void reloadModules() {
|
||||||
|
getLogger().info("[StatusAPI] Reload von Scoreboard und Tablist...");
|
||||||
|
|
||||||
|
net.viper.status.module.Module sbMod = moduleManager.getModule("ScoreboardModule");
|
||||||
|
net.viper.status.module.Module tabMod = moduleManager.getModule("TablistModule");
|
||||||
|
|
||||||
|
if (sbMod != null) sbMod.onDisable(this);
|
||||||
|
if (tabMod != null) tabMod.onDisable(this);
|
||||||
|
|
||||||
|
// Neue Instanzen erstellen und registrieren
|
||||||
|
net.viper.status.modules.scoreboard.ScoreboardModule newSb = new net.viper.status.modules.scoreboard.ScoreboardModule();
|
||||||
|
net.viper.status.modules.tablist.TablistModule newTab = new net.viper.status.modules.tablist.TablistModule();
|
||||||
|
|
||||||
|
moduleManager.replaceModule("ScoreboardModule", newSb);
|
||||||
|
moduleManager.replaceModule("TablistModule", newTab);
|
||||||
|
|
||||||
|
newSb.onEnable(this);
|
||||||
|
newTab.onEnable(this);
|
||||||
|
|
||||||
|
// TPS-Fallback neu verbinden
|
||||||
|
try {
|
||||||
|
net.viper.status.modules.network.NetworkInfoModule nim =
|
||||||
|
(net.viper.status.modules.network.NetworkInfoModule) moduleManager.getModule("NetworkInfoModule");
|
||||||
|
if (nim != null) newSb.setNetworkInfoModule(nim);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
scanAndPublishPapiTokens();
|
||||||
|
getLogger().info("[StatusAPI] Reload abgeschlossen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /statusapi Befehl ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static class StatusAPICommand extends net.md_5.bungee.api.plugin.Command {
|
||||||
|
|
||||||
|
private final StatusAPI plugin;
|
||||||
|
|
||||||
|
StatusAPICommand(StatusAPI plugin) {
|
||||||
|
super("statusapi", "statusapi.admin", "sapi");
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(net.md_5.bungee.api.CommandSender sender, String[] args) {
|
||||||
|
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
|
||||||
|
send(sender, "&8&m──────────────────────────────────────────");
|
||||||
|
send(sender, "&6&lStatusAPI &7| Befehle");
|
||||||
|
send(sender, "&e/statusapi reload &7– Scoreboard & Tablist neu laden");
|
||||||
|
send(sender, "&8&m──────────────────────────────────────────");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender.hasPermission("statusapi.admin")) {
|
||||||
|
send(sender, "&cKeine Berechtigung.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "reload":
|
||||||
|
send(sender, "&7Lade &6Scoreboard &7und &6Tablist &7neu...");
|
||||||
|
plugin.reloadModules();
|
||||||
|
send(sender, "&aScoreboard &7und &aTablist &7wurden neu geladen.");
|
||||||
|
send(sender, "&7PAPI-Tokens erkannt: &e" + papiTokensJson);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
send(sender, "&cUnbekannter Unterbefehl. Nutze &e/statusapi help&c.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void send(net.md_5.bungee.api.CommandSender s, String text) {
|
||||||
|
s.sendMessage(new net.md_5.bungee.api.chat.TextComponent(
|
||||||
|
net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', text)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,14 @@ public class ModuleManager {
|
|||||||
return modules.get(name.toLowerCase());
|
return modules.get(name.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ersetzt ein bestehendes Modul durch eine neue Instanz (für Reload).
|
||||||
|
* Das alte Modul muss bereits deaktiviert worden sein.
|
||||||
|
*/
|
||||||
|
public void replaceModule(String name, Module newModule) {
|
||||||
|
modules.put(name.toLowerCase(), newModule);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends Module> T getModule(Class<T> clazz) {
|
public <T extends Module> T getModule(Class<T> clazz) {
|
||||||
for (Module m : modules.values()) {
|
for (Module m : modules.values()) {
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
public static final java.util.concurrent.atomic.AtomicInteger ticketRatingBad = new java.util.concurrent.atomic.AtomicInteger(0);
|
public static final java.util.concurrent.atomic.AtomicInteger ticketRatingBad = new java.util.concurrent.atomic.AtomicInteger(0);
|
||||||
|
|
||||||
private final ConcurrentHashMap<UUID, Long> joinTimes = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<UUID, Long> joinTimes = new ConcurrentHashMap<>();
|
||||||
|
/** Aktuell gerenderter Spieler – für PAPI-Auflösung in ph() */
|
||||||
|
private UUID currentPlayerUuid = null;
|
||||||
// Spieler, die das Scoreboard ausgeblendet haben
|
// Spieler, die das Scoreboard ausgeblendet haben
|
||||||
/** FIX: Referenz auf NetworkInfoModule für TPS-Fallback */
|
/** FIX: Referenz auf NetworkInfoModule für TPS-Fallback */
|
||||||
private net.viper.status.modules.network.NetworkInfoModule networkInfoModule = null;
|
private net.viper.status.modules.network.NetworkInfoModule networkInfoModule = null;
|
||||||
@@ -302,7 +304,7 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
team.setDisplayName(Either.right(new net.md_5.bungee.api.chat.TextComponent("")));
|
team.setDisplayName(Either.right(new net.md_5.bungee.api.chat.TextComponent("")));
|
||||||
team.setNameTagVisibility(Either.right(net.md_5.bungee.protocol.packet.Team.NameTagVisibility.ALWAYS));
|
team.setNameTagVisibility(Either.right(net.md_5.bungee.protocol.packet.Team.NameTagVisibility.ALWAYS));
|
||||||
team.setCollisionRule(Either.right(net.md_5.bungee.protocol.packet.Team.CollisionRule.ALWAYS));
|
team.setCollisionRule(Either.right(net.md_5.bungee.protocol.packet.Team.CollisionRule.ALWAYS));
|
||||||
team.setColor(21);
|
team.setColor(Optional.of(21));
|
||||||
team.setFriendlyFire((byte) 3);
|
team.setFriendlyFire((byte) 3);
|
||||||
sendPkt.invoke(p, team);
|
sendPkt.invoke(p, team);
|
||||||
}
|
}
|
||||||
@@ -466,6 +468,7 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
boolean hasTicker = !tickerText.isEmpty() && !isAdmin && !isSupporter;
|
boolean hasTicker = !tickerText.isEmpty() && !isAdmin && !isSupporter;
|
||||||
if (hasTicker) lines.add(ticker(rawTicker, tOff, rIdx));
|
if (hasTicker) lines.add(ticker(rawTicker, tOff, rIdx));
|
||||||
// Maximale Inhaltszeilen: MAX_LINES insgesamt (Ticker zählt als eine)
|
// Maximale Inhaltszeilen: MAX_LINES insgesamt (Ticker zählt als eine)
|
||||||
|
currentPlayerUuid = id; // für PAPI-Auflösung in ph()
|
||||||
for (String tpl : srcLines) {
|
for (String tpl : srcLines) {
|
||||||
if (lines.size() >= MAX_LINES) break;
|
if (lines.size() >= MAX_LINES) break;
|
||||||
lines.add(c(ph(tpl, pn, rank, money, srv, comp, hp, hpNum, ping, online, maxpl, tps, ram, time, playtime,
|
lines.add(c(ph(tpl, pn, rank, money, srv, comp, hp, hpNum, ping, online, maxpl, tps, ram, time, playtime,
|
||||||
@@ -564,7 +567,7 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
team.setDisplayName(Either.right(new net.md_5.bungee.api.chat.TextComponent("")));
|
team.setDisplayName(Either.right(new net.md_5.bungee.api.chat.TextComponent("")));
|
||||||
team.setNameTagVisibility(Either.right(NameTagVisibility.ALWAYS));
|
team.setNameTagVisibility(Either.right(NameTagVisibility.ALWAYS));
|
||||||
team.setCollisionRule(Either.right(CollisionRule.ALWAYS));
|
team.setCollisionRule(Either.right(CollisionRule.ALWAYS));
|
||||||
team.setColor(21); // RESET
|
team.setColor(Optional.of(21)); // RESET
|
||||||
team.setFriendlyFire((byte) 3);
|
team.setFriendlyFire((byte) 3);
|
||||||
team.setPlayers(new String[]{ ENTRIES[i] });
|
team.setPlayers(new String[]{ ENTRIES[i] });
|
||||||
sendPkt.invoke(p, team);
|
sendPkt.invoke(p, team);
|
||||||
@@ -835,7 +838,9 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
String ticketMyOpen, String ticketTotalOpen, String ticketTotalClaimed,
|
String ticketMyOpen, String ticketTotalOpen, String ticketTotalClaimed,
|
||||||
String ticketRatingGood, String ticketRatingBad, String ticketRatingPct) {
|
String ticketRatingGood, String ticketRatingBad, String ticketRatingPct) {
|
||||||
if (tpl == null) return " ";
|
if (tpl == null) return " ";
|
||||||
String s = tpl
|
// PAPI-Werte zuerst einsetzen; native Tokens überschreiben sie danach
|
||||||
|
String s = resolvePapiPlaceholders(tpl, currentPlayerUuid);
|
||||||
|
s = s
|
||||||
.replace("%player%", player) .replace("%rank%", rank)
|
.replace("%player%", player) .replace("%rank%", rank)
|
||||||
.replace("%money%", money) .replace("%server%", server)
|
.replace("%money%", money) .replace("%server%", server)
|
||||||
.replace("%compass%", compass) .replace("%health%", health)
|
.replace("%compass%", compass) .replace("%health%", health)
|
||||||
@@ -864,6 +869,31 @@ public class ScoreboardModule implements Module, Listener {
|
|||||||
return s.isEmpty() ? " " : s;
|
return s.isEmpty() ? " " : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String resolvePapiPlaceholders(String text, UUID uuid) {
|
||||||
|
if (text == null || !text.contains("%")) return text;
|
||||||
|
if (uuid == null) return text;
|
||||||
|
java.util.Map<String, String> papiMap = net.viper.status.StatusAPI.playerPapi.get(uuid);
|
||||||
|
if (papiMap == null || papiMap.isEmpty()) return text;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int i = 0;
|
||||||
|
while (i < text.length()) {
|
||||||
|
int start = text.indexOf('%', i);
|
||||||
|
if (start < 0) { sb.append(text.substring(i)); break; }
|
||||||
|
int end = text.indexOf('%', start + 1);
|
||||||
|
if (end < 0) { sb.append(text.substring(i)); break; }
|
||||||
|
String token = text.substring(start + 1, end);
|
||||||
|
if (papiMap.containsKey(token)) {
|
||||||
|
sb.append(text, i, start);
|
||||||
|
sb.append(papiMap.get(token));
|
||||||
|
i = end + 1;
|
||||||
|
} else {
|
||||||
|
sb.append(text, i, end + 1);
|
||||||
|
i = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// ── Daten-Helfer ─────────────────────────────────────────────────────────
|
// ── Daten-Helfer ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private String getRank(ProxiedPlayer p) {
|
private String getRank(ProxiedPlayer p) {
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ public class TablistModule implements Module, Listener {
|
|||||||
private boolean compactFooter4Spacer = false;
|
private boolean compactFooter4Spacer = false;
|
||||||
|
|
||||||
private String colorSrvHeader = "&6&l";
|
private String colorSrvHeader = "&6&l";
|
||||||
|
private boolean showFooterServerList = true;
|
||||||
|
private String columnHeaderMode = "none";
|
||||||
|
private final Map<String, String> serverSymbols = new LinkedHashMap<>();
|
||||||
private String timeFormat = "HH:mm:ss / h:mm a";
|
private String timeFormat = "HH:mm:ss / h:mm a";
|
||||||
private String timeZone = "Europe/Berlin";
|
private String timeZone = "Europe/Berlin";
|
||||||
private SimpleDateFormat sdf;
|
private SimpleDateFormat sdf;
|
||||||
@@ -307,7 +310,7 @@ public class TablistModule implements Module, Listener {
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
appendLine(sb, compactFooter1, compactFooter1Spacer, viewer, srv, world, rank, time, balance, online);
|
appendLine(sb, compactFooter1, compactFooter1Spacer, viewer, srv, world, rank, time, balance, online);
|
||||||
List<String> servers = getServerOrder();
|
List<String> servers = getServerOrder();
|
||||||
if (!servers.isEmpty()) {
|
if (showFooterServerList && !servers.isEmpty()) {
|
||||||
StringBuilder sLine = new StringBuilder();
|
StringBuilder sLine = new StringBuilder();
|
||||||
for (String sName : servers) {
|
for (String sName : servers) {
|
||||||
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
||||||
@@ -340,6 +343,7 @@ public class TablistModule implements Module, Listener {
|
|||||||
for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
|
for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
|
||||||
|
|
||||||
boolean compact = "compact".equalsIgnoreCase(layoutMode);
|
boolean compact = "compact".equalsIgnoreCase(layoutMode);
|
||||||
|
boolean useSlotHeader = "full".equalsIgnoreCase(columnHeaderMode);
|
||||||
|
|
||||||
// Info-Spalte (nur classic)
|
// Info-Spalte (nur classic)
|
||||||
if (!compact) {
|
if (!compact) {
|
||||||
@@ -372,16 +376,18 @@ public class TablistModule implements Module, Listener {
|
|||||||
List<String> servers = getServerOrder();
|
List<String> servers = getServerOrder();
|
||||||
int startCol = compact ? 0 : 1;
|
int startCol = compact ? 0 : 1;
|
||||||
for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) {
|
for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) {
|
||||||
int base = col * rows, row = 0;
|
int base = col * rows;
|
||||||
|
int row = 0;
|
||||||
String sName = servers.get(col - startCol);
|
String sName = servers.get(col - startCol);
|
||||||
row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
|
if (useSlotHeader) row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
|
||||||
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) {
|
for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) {
|
||||||
if (row >= rows) break;
|
if (row >= rows) break;
|
||||||
String prefix = getLuckPermsPrefix(p);
|
String prefix = getLuckPermsPrefix(p);
|
||||||
set(texts, base, row, prefix.isEmpty() ? c("&7" + p.getName()) : c(prefix + "&r " + p.getName()));
|
String symbol = getServerSymbol(p);
|
||||||
// Skin aus Cache – immer aktuell
|
String nameStr = p.getName() + (symbol.isEmpty() ? "" : " " + symbol);
|
||||||
|
set(texts, base, row, prefix.isEmpty() ? c("&7" + nameStr) : c(prefix + "&r " + nameStr));
|
||||||
net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId());
|
net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId());
|
||||||
skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN;
|
skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN;
|
||||||
pings[base + row] = p.getPing() < 0 ? 1 : p.getPing();
|
pings[base + row] = p.getPing() < 0 ? 1 : p.getPing();
|
||||||
@@ -412,14 +418,20 @@ public class TablistModule implements Module, Listener {
|
|||||||
if (sendPacketQueuedMethod == null) return;
|
if (sendPacketQueuedMethod == null) return;
|
||||||
try {
|
try {
|
||||||
Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
|
Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
|
||||||
if (online.isEmpty()) return;
|
List<UUID> toHide = new ArrayList<>();
|
||||||
|
for (ProxiedPlayer p : online) toHide.add(p.getUniqueId());
|
||||||
|
for (String srvName : ProxyServer.getInstance().getServers().keySet()) {
|
||||||
|
try {
|
||||||
|
toHide.add(UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8)));
|
||||||
|
toHide.add(UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
if (toHide.isEmpty()) return;
|
||||||
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
|
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
|
||||||
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
|
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
|
||||||
Item[] items = new Item[online.size()];
|
Item[] items = new Item[toHide.size()];
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (ProxiedPlayer p : online) {
|
for (UUID uuid : toHide) { Item it = new Item(); it.setUuid(uuid); it.setListed(false); items[idx++] = it; }
|
||||||
Item it = new Item(); it.setUuid(p.getUniqueId()); it.setListed(false); items[idx++] = it;
|
|
||||||
}
|
|
||||||
pkt.setItems(items);
|
pkt.setItems(items);
|
||||||
sendPacketQueuedMethod.invoke(viewer, pkt);
|
sendPacketQueuedMethod.invoke(viewer, pkt);
|
||||||
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage()); }
|
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage()); }
|
||||||
@@ -461,6 +473,13 @@ public class TablistModule implements Module, Listener {
|
|||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private String getServerSymbol(ProxiedPlayer player) {
|
||||||
|
if (serverSymbols.isEmpty() || player.getServer() == null) return "";
|
||||||
|
String raw = serverSymbols.get(player.getServer().getInfo().getName().toLowerCase());
|
||||||
|
if (raw == null || raw.isEmpty()) return "";
|
||||||
|
return c(raw);
|
||||||
|
}
|
||||||
|
|
||||||
private net.md_5.bungee.protocol.data.Property[] fetchSkin(ProxiedPlayer player) {
|
private net.md_5.bungee.protocol.data.Property[] fetchSkin(ProxiedPlayer player) {
|
||||||
try {
|
try {
|
||||||
Object pending = player.getPendingConnection();
|
Object pending = player.getPendingConnection();
|
||||||
@@ -546,7 +565,7 @@ public class TablistModule implements Module, Listener {
|
|||||||
Object cache = usr.getClass().getMethod("getCachedData").invoke(usr);
|
Object cache = usr.getClass().getMethod("getCachedData").invoke(usr);
|
||||||
Object meta = cache.getClass().getMethod("getMetaData", qo).invoke(cache, opts);
|
Object meta = cache.getClass().getMethod("getMetaData", qo).invoke(cache, opts);
|
||||||
Object pfx = meta.getClass().getMethod("getPrefix").invoke(meta);
|
Object pfx = meta.getClass().getMethod("getPrefix").invoke(meta);
|
||||||
if (pfx != null) return ChatColor.translateAlternateColorCodes('&', pfx.toString());
|
if (pfx != null) return c(pfx.toString());
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
return "";
|
return "";
|
||||||
@@ -563,10 +582,37 @@ public class TablistModule implements Module, Listener {
|
|||||||
|
|
||||||
private String replacePlaceholders(String text, ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
|
private String replacePlaceholders(String text, ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
|
||||||
if (text == null) return "";
|
if (text == null) return "";
|
||||||
return text.replace("%player%", viewer.getName()).replace("%rank%", rank)
|
// PAPI zuerst, native Tokens danach (überschreiben PAPI-Werte falls gleicher Name)
|
||||||
|
String result = resolvePapiPlaceholders(text, viewer.getUniqueId());
|
||||||
|
result = result.replace("%player%", viewer.getName()).replace("%rank%", rank)
|
||||||
.replace("%server%", srv).replace("%world%", world).replace("%time%", time)
|
.replace("%server%", srv).replace("%world%", world).replace("%time%", time)
|
||||||
.replace("%balance%", balance).replace("%ping%", String.valueOf(viewer.getPing()))
|
.replace("%balance%", balance).replace("%ping%", String.valueOf(viewer.getPing()))
|
||||||
.replace("%online%", String.valueOf(online));
|
.replace("%online%", String.valueOf(online));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolvePapiPlaceholders(String text, UUID uuid) {
|
||||||
|
if (text == null || !text.contains("%")) return text;
|
||||||
|
java.util.Map<String, String> papiMap = net.viper.status.StatusAPI.playerPapi.get(uuid);
|
||||||
|
if (papiMap == null || papiMap.isEmpty()) return text;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int i = 0;
|
||||||
|
while (i < text.length()) {
|
||||||
|
int start = text.indexOf('%', i);
|
||||||
|
if (start < 0) { sb.append(text.substring(i)); break; }
|
||||||
|
int end = text.indexOf('%', start + 1);
|
||||||
|
if (end < 0) { sb.append(text.substring(i)); break; }
|
||||||
|
String token = text.substring(start + 1, end);
|
||||||
|
if (papiMap.containsKey(token)) {
|
||||||
|
sb.append(text, i, start);
|
||||||
|
sb.append(papiMap.get(token));
|
||||||
|
i = end + 1;
|
||||||
|
} else {
|
||||||
|
sb.append(text, i, end + 1);
|
||||||
|
i = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int set(String[] arr, int base, int row, String text) {
|
private int set(String[] arr, int base, int row, String text) {
|
||||||
@@ -580,7 +626,8 @@ public class TablistModule implements Module, Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String replaceHexColors(String text) {
|
private static String replaceHexColors(String text) {
|
||||||
if (text == null || (!text.contains("&#") && !text.contains("{#"))) return text;
|
if (text == null) return null;
|
||||||
|
if (!text.contains("&#") && !text.contains("{#") && !text.contains("<#")) return text;
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < text.length()) {
|
while (i < text.length()) {
|
||||||
@@ -603,6 +650,18 @@ public class TablistModule implements Module, Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Format 3: <#RRGGBB>
|
||||||
|
if (i + 8 < text.length() && text.charAt(i) == '<' && text.charAt(i+1) == '#') {
|
||||||
|
int end = text.indexOf('>', i+2);
|
||||||
|
if (end == i+8) {
|
||||||
|
String hex = text.substring(i+2, i+8);
|
||||||
|
if (hex.matches("[0-9a-fA-F]{6}")) {
|
||||||
|
sb.append('\u00A7').append('x');
|
||||||
|
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
|
||||||
|
i += 9; continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
sb.append(text.charAt(i)); i++;
|
sb.append(text.charAt(i)); i++;
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
@@ -653,6 +712,10 @@ public class TablistModule implements Module, Listener {
|
|||||||
"tablist.compact.footer.line4=\n" +
|
"tablist.compact.footer.line4=\n" +
|
||||||
"tablist.compact.footer.line4.spacer=false\n\n" +
|
"tablist.compact.footer.line4.spacer=false\n\n" +
|
||||||
"tablist.color.server_header=&6&l\n" +
|
"tablist.color.server_header=&6&l\n" +
|
||||||
|
"# column_header: full=großer Header | none=kein Header (Zeile 0 frei) | small=nur im Footer\n" +
|
||||||
|
"tablist.column_header=none\n" +
|
||||||
|
"# Server-Liste im Footer anzeigen (true/false)\n" +
|
||||||
|
"tablist.compact.footer.serverlist=true\n" +
|
||||||
"tablist.time_format=HH:mm:ss / h:mm a\n" +
|
"tablist.time_format=HH:mm:ss / h:mm a\n" +
|
||||||
"tablist.timezone=Europe/Berlin\n\n" +
|
"tablist.timezone=Europe/Berlin\n\n" +
|
||||||
"# ── Info-Spalte (nur classic) ────────────────────────────────────────\n" +
|
"# ── Info-Spalte (nur classic) ────────────────────────────────────────\n" +
|
||||||
@@ -679,7 +742,11 @@ public class TablistModule implements Module, Listener {
|
|||||||
"tablist.info.teamspeak.enabled=true\n" +
|
"tablist.info.teamspeak.enabled=true\n" +
|
||||||
"tablist.info.teamspeak.label=&b&lTeamspeak:\n" +
|
"tablist.info.teamspeak.label=&b&lTeamspeak:\n" +
|
||||||
"tablist.info.teamspeak.type=teamspeak\n" +
|
"tablist.info.teamspeak.type=teamspeak\n" +
|
||||||
"tablist.info.teamspeak.value=&fts.viper-network.de\n";
|
"tablist.info.teamspeak.value=&fts.viper-network.de\n" +
|
||||||
|
"\n# Server-Symbole hinter dem Spielernamen\n" +
|
||||||
|
"# Format: tablist.symbol.<servername>=&FarbCode Symbol\n" +
|
||||||
|
"tablist.symbol.lobby=&f\uD83C\uDFE0\n" +
|
||||||
|
"tablist.symbol.sv1=&6\u26CF\uFE0F\n";
|
||||||
try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); }
|
try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); }
|
||||||
catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config: " + e.getMessage()); }
|
catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config: " + e.getMessage()); }
|
||||||
}
|
}
|
||||||
@@ -723,6 +790,8 @@ public class TablistModule implements Module, Listener {
|
|||||||
compactFooter1Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line1.spacer", "false"));
|
compactFooter1Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line1.spacer", "false"));
|
||||||
compactFooter4Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line4.spacer", "false"));
|
compactFooter4Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line4.spacer", "false"));
|
||||||
colorSrvHeader = get.apply("tablist.color.server_header", colorSrvHeader);
|
colorSrvHeader = get.apply("tablist.color.server_header", colorSrvHeader);
|
||||||
|
showFooterServerList = Boolean.parseBoolean(get.apply("tablist.compact.footer.serverlist", "true"));
|
||||||
|
columnHeaderMode = get.apply("tablist.column_header", "none").trim().toLowerCase();
|
||||||
timeFormat = get.apply("tablist.time_format", timeFormat);
|
timeFormat = get.apply("tablist.time_format", timeFormat);
|
||||||
timeZone = get.apply("tablist.timezone", timeZone);
|
timeZone = get.apply("tablist.timezone", timeZone);
|
||||||
try { sdf = new SimpleDateFormat(timeFormat); sdf.setTimeZone(java.util.TimeZone.getTimeZone(timeZone)); }
|
try { sdf = new SimpleDateFormat(timeFormat); sdf.setTimeZone(java.util.TimeZone.getTimeZone(timeZone)); }
|
||||||
@@ -759,5 +828,17 @@ public class TablistModule implements Module, Listener {
|
|||||||
infoEntries.add(new InfoEntry("&b&lTime:", "time", "", true));
|
infoEntries.add(new InfoEntry("&b&lTime:", "time", "", true));
|
||||||
infoEntries.add(new InfoEntry("&b&lTeamspeak:", "teamspeak", "&fts.viper-network.de", true));
|
infoEntries.add(new InfoEntry("&b&lTeamspeak:", "teamspeak", "&fts.viper-network.de", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server-Symbole
|
||||||
|
serverSymbols.clear();
|
||||||
|
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (key.startsWith("tablist.symbol.")) {
|
||||||
|
String srvName = key.substring("tablist.symbol.".length()).trim().toLowerCase();
|
||||||
|
String symbol = entry.getValue().trim();
|
||||||
|
if (!srvName.isEmpty() && !symbol.isEmpty())
|
||||||
|
serverSymbols.put(srvName, symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ networkinfo.include_player_names=false
|
|||||||
|
|
||||||
# Discord Webhook fuer Status-, Warn- und Attack-Meldungen
|
# Discord Webhook fuer Status-, Warn- und Attack-Meldungen
|
||||||
networkinfo.webhook.enabled=true
|
networkinfo.webhook.enabled=true
|
||||||
networkinfo.webhook.url=https://discord.com/api/webhooks/1488630083164831844/o7L5Mhy5P_xE_n-2Dq9usIVX40o7fCpPHgaGQOVIQHjfK7SDrVJbdeZM-G6vVRVhvzT9
|
networkinfo.webhook.url=
|
||||||
networkinfo.webhook.username=StatusAPI
|
networkinfo.webhook.username=StatusAPI
|
||||||
networkinfo.webhook.thumbnail_url=
|
networkinfo.webhook.thumbnail_url=
|
||||||
networkinfo.webhook.notify_start_stop=true
|
networkinfo.webhook.notify_start_stop=true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: StatusAPI
|
name: StatusAPI
|
||||||
main: net.viper.status.StatusAPI
|
main: net.viper.status.StatusAPI
|
||||||
version: 4.1.0
|
version: 4.1.1
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule
|
description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule
|
||||||
# Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung
|
# Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung
|
||||||
@@ -16,6 +16,13 @@ commands:
|
|||||||
usage: /scoreboard [hide|show|player|admin]
|
usage: /scoreboard [hide|show|player|admin]
|
||||||
aliases: [sb, togglesb]
|
aliases: [sb, togglesb]
|
||||||
|
|
||||||
|
# ── StatusAPI Admin ───────────────────────────────────────
|
||||||
|
statusapi:
|
||||||
|
description: StatusAPI verwalten (Reload, Info)
|
||||||
|
usage: /statusapi reload
|
||||||
|
aliases: [sapi]
|
||||||
|
permission: statusapi.admin
|
||||||
|
|
||||||
# /pay und /ecoadmin werden von NexEco (Spigot) verwaltet
|
# /pay und /ecoadmin werden von NexEco (Spigot) verwaltet
|
||||||
|
|
||||||
# ── VanishModule ──────────────────────────────────────────
|
# ── VanishModule ──────────────────────────────────────────
|
||||||
@@ -192,6 +199,10 @@ commands:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
# ── StatusAPI Core ────────────────────────────────────────
|
# ── StatusAPI Core ────────────────────────────────────────
|
||||||
|
statusapi.admin:
|
||||||
|
description: Zugang zu StatusAPI-Administrationsbefehlen (reload etc.)
|
||||||
|
default: op
|
||||||
|
|
||||||
statusapi.update.notify:
|
statusapi.update.notify:
|
||||||
description: Erlaubt Update-Benachrichtigungen
|
description: Erlaubt Update-Benachrichtigungen
|
||||||
default: op
|
default: op
|
||||||
|
|||||||
@@ -6,14 +6,17 @@
|
|||||||
|
|
||||||
<groupId>net.viper</groupId>
|
<groupId>net.viper</groupId>
|
||||||
<artifactId>StatusAPIBridge</artifactId>
|
<artifactId>StatusAPIBridge</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.2</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<!-- Niedrigste gemeinsame Basis: Java 17 (läuft auf 1.21.1 und 26.1.2) -->
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<!-- Standard-API-Version (wird durch Profile überschrieben) -->
|
||||||
|
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
@@ -25,14 +28,18 @@
|
|||||||
<id>vault-repo</id>
|
<id>vault-repo</id>
|
||||||
<url>https://nexus.hc.to/content/repositories/pub_releases/</url>
|
<url>https://nexus.hc.to/content/repositories/pub_releases/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>placeholderapi</id>
|
||||||
|
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Spigot API -->
|
<!-- Spigot API – Version wird durch Profil gesteuert -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.spigotmc</groupId>
|
<groupId>org.spigotmc</groupId>
|
||||||
<artifactId>spigot-api</artifactId>
|
<artifactId>spigot-api</artifactId>
|
||||||
<version>1.21-R0.1-SNAPSHOT</version>
|
<version>${spigot.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Vault -->
|
<!-- Vault -->
|
||||||
@@ -42,11 +49,56 @@
|
|||||||
<version>1.7</version>
|
<version>1.7</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- PlaceholderAPI (optional – per Reflection genutzt) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.clip</groupId>
|
||||||
|
<artifactId>placeholderapi</artifactId>
|
||||||
|
<version>2.11.6</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════════════════════════
|
||||||
|
Maven-Profile für Multi-Version-Build
|
||||||
|
mvn package → mc-1.21.1 (Standard)
|
||||||
|
mvn package -P mc-26.1.2 → mc-26.1.2
|
||||||
|
══════════════════════════════════════════════════════════════════════ -->
|
||||||
|
<profiles>
|
||||||
|
|
||||||
|
<!-- Profil 1: Minecraft 1.21.1 (Standard) -->
|
||||||
|
<profile>
|
||||||
|
<id>mc-1.21.1</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<!-- Profil 2: Minecraft 26.1.2 -->
|
||||||
|
<profile>
|
||||||
|
<id>mc-26.1.2</id>
|
||||||
|
<properties>
|
||||||
|
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
</profiles>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>StatusAPIBridge</finalName>
|
<finalName>StatusAPIBridge</finalName>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -41,6 +42,16 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
private final Map<UUID, String> lastPushedStats = new ConcurrentHashMap<>();
|
private final Map<UUID, String> lastPushedStats = new ConcurrentHashMap<>();
|
||||||
private String lastPushedTicketGlobal = "";
|
private String lastPushedTicketGlobal = "";
|
||||||
|
|
||||||
|
// ── PlaceholderAPI ────────────────────────────────────────────────────────
|
||||||
|
private final Set<String> papiTokens = new java.util.LinkedHashSet<>();
|
||||||
|
private final Map<UUID, String> lastPapiValues = new ConcurrentHashMap<>();
|
||||||
|
private boolean papiEnabled = false;
|
||||||
|
|
||||||
|
// ── Versions-Detection ────────────────────────────────────────────────────
|
||||||
|
// true = 1.21.x-Modus (Spigot/Paper)
|
||||||
|
// false = 26.1.x-Modus (neuere Server-Version, kein NMS-Fallback)
|
||||||
|
private boolean isLegacyMode = true;
|
||||||
|
|
||||||
private final ExecutorService httpExecutor = Executors.newSingleThreadExecutor(r -> {
|
private final ExecutorService httpExecutor = Executors.newSingleThreadExecutor(r -> {
|
||||||
Thread t = new Thread(r, "StatusAPIBridge-HTTP");
|
Thread t = new Thread(r, "StatusAPIBridge-HTTP");
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
@@ -50,6 +61,7 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
detectMinecraftVersion();
|
||||||
statusApiUrl = getConfig().getString("statusapi-url", "http://127.0.0.1:9191").trim();
|
statusApiUrl = getConfig().getString("statusapi-url", "http://127.0.0.1:9191").trim();
|
||||||
pushDelayTicks = getConfig().getInt("push-delay-ticks", 40);
|
pushDelayTicks = getConfig().getInt("push-delay-ticks", 40);
|
||||||
liveSyncIntervalTicks = Math.max(20, getConfig().getInt("live-sync-interval-ticks", 20));
|
liveSyncIntervalTicks = Math.max(20, getConfig().getInt("live-sync-interval-ticks", 20));
|
||||||
@@ -70,6 +82,28 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
// TicketSystem-Daten alle 5 Sekunden pushen (100 Ticks)
|
// TicketSystem-Daten alle 5 Sekunden pushen (100 Ticks)
|
||||||
Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::pushTicketData, 100L, 100L);
|
Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::pushTicketData, 100L, 100L);
|
||||||
|
|
||||||
|
// PlaceholderAPI-Integration
|
||||||
|
papiEnabled = getServer().getPluginManager().getPlugin("PlaceholderAPI") != null;
|
||||||
|
if (papiEnabled) {
|
||||||
|
// Tokens alle 30s von StatusAPI holen, nur bei Änderung loggen
|
||||||
|
Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> {
|
||||||
|
Set<String> before = new java.util.LinkedHashSet<>(papiTokens);
|
||||||
|
boolean fetched = fetchPapiTokensFromStatusAPI();
|
||||||
|
if (fetched && !papiTokens.equals(before)) {
|
||||||
|
if (papiTokens.isEmpty()) {
|
||||||
|
getLogger().info("[PAPI] Keine Placeholder in der StatusAPI-Config gefunden.");
|
||||||
|
} else {
|
||||||
|
getLogger().info("[PAPI] " + papiTokens.size() + " Placeholder erkannt: " + papiTokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 40L, 600L); // nach 2s starten, alle 30s wiederholen
|
||||||
|
|
||||||
|
// Sync-Task läuft dauerhaft – tut nichts wenn papiTokens leer
|
||||||
|
Bukkit.getScheduler().runTaskTimer(this, this::syncPapiValues, scoreboardSyncIntervalTicks, scoreboardSyncIntervalTicks);
|
||||||
|
} else {
|
||||||
|
getLogger().info("[PAPI] PlaceholderAPI nicht gefunden – Placeholder werden nicht aufgelöst.");
|
||||||
|
}
|
||||||
|
|
||||||
getLogger().info("StatusAPIBridge gestartet. Ziel: " + statusApiUrl);
|
getLogger().info("StatusAPIBridge gestartet. Ziel: " + statusApiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +130,7 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
if (!player.isOnline()) return;
|
if (!player.isOnline()) return;
|
||||||
if (economy != null) pushEconomy(player);
|
if (economy != null) pushEconomy(player);
|
||||||
pushPlayerScoreboardData(player);
|
pushPlayerScoreboardData(player);
|
||||||
|
if (papiEnabled && !papiTokens.isEmpty()) pushPapiValues(player);
|
||||||
}, pushDelayTicks);
|
}, pushDelayTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,26 +145,27 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
lastPushedWorld.remove(id);
|
lastPushedWorld.remove(id);
|
||||||
lastPushedData.remove(id);
|
lastPushedData.remove(id);
|
||||||
lastPushedStats.remove(id);
|
lastPushedStats.remove(id);
|
||||||
|
lastPapiValues.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
public void onDamage(EntityDamageEvent e) {
|
public void onDamage(EntityDamageEvent e) {
|
||||||
if (!(e.getEntity() instanceof Player)) return;
|
if (!(e.getEntity() instanceof Player player)) return;
|
||||||
Player player = (Player) e.getEntity();
|
|
||||||
Bukkit.getScheduler().runTaskLater(this,
|
Bukkit.getScheduler().runTaskLater(this,
|
||||||
() -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
|
() -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
public void onHeal(EntityRegainHealthEvent e) {
|
public void onHeal(EntityRegainHealthEvent e) {
|
||||||
if (!(e.getEntity() instanceof Player)) return;
|
if (!(e.getEntity() instanceof Player player)) return;
|
||||||
Player player = (Player) e.getEntity();
|
|
||||||
Bukkit.getScheduler().runTaskLater(this,
|
Bukkit.getScheduler().runTaskLater(this,
|
||||||
() -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
|
() -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
public void onMove(PlayerMoveEvent e) {
|
public void onMove(PlayerMoveEvent e) {
|
||||||
|
// getTo() kann in 1.20.5+ bei reinen Head-Rotationen null sein
|
||||||
|
if (e.getTo() == null) return;
|
||||||
if (e.getFrom().getYaw() == e.getTo().getYaw()) return;
|
if (e.getFrom().getYaw() == e.getTo().getYaw()) return;
|
||||||
pushCompassIfChanged(e.getPlayer());
|
pushCompassIfChanged(e.getPlayer());
|
||||||
}
|
}
|
||||||
@@ -368,6 +404,79 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── PlaceholderAPI ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private boolean fetchPapiTokensFromStatusAPI() {
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
java.net.URL url = new java.net.URI(statusApiUrl + "/papi/tokens").toURL();
|
||||||
|
java.net.HttpURLConnection c = (java.net.HttpURLConnection) url.openConnection();
|
||||||
|
c.setRequestMethod("GET");
|
||||||
|
c.setConnectTimeout(3000);
|
||||||
|
c.setReadTimeout(3000);
|
||||||
|
if (c.getResponseCode() != 200) { c.disconnect(); return false; }
|
||||||
|
java.io.InputStream is = c.getInputStream();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int ch; while ((ch = is.read()) != -1) sb.append((char) ch);
|
||||||
|
c.disconnect();
|
||||||
|
String body = sb.toString().trim();
|
||||||
|
papiTokens.clear();
|
||||||
|
if (body.startsWith("[") && body.endsWith("]")) {
|
||||||
|
String inner = body.substring(1, body.length() - 1).trim();
|
||||||
|
if (!inner.isEmpty()) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < inner.length()) {
|
||||||
|
while (i < inner.length() && inner.charAt(i) != '"') i++;
|
||||||
|
if (i >= inner.length()) break;
|
||||||
|
i++;
|
||||||
|
StringBuilder token = new StringBuilder();
|
||||||
|
while (i < inner.length() && inner.charAt(i) != '"') {
|
||||||
|
char c2 = inner.charAt(i++);
|
||||||
|
if (c2 == '\\' && i < inner.length()) c2 = inner.charAt(i++);
|
||||||
|
token.append(c2);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
if (token.length() > 0) papiTokens.add(token.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncPapiValues() {
|
||||||
|
if (!papiEnabled || papiTokens.isEmpty()) return;
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers()) pushPapiValues(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushPapiValues(Player p) {
|
||||||
|
try {
|
||||||
|
Class<?> papiClass = Class.forName("me.clip.placeholderapi.PlaceholderAPI");
|
||||||
|
java.lang.reflect.Method setPlaceholders = papiClass.getMethod("setPlaceholders", Player.class, String.class);
|
||||||
|
StringBuilder jsonValues = new StringBuilder();
|
||||||
|
for (String token : papiTokens) {
|
||||||
|
String resolved = (String) setPlaceholders.invoke(null, p, "%" + token + "%");
|
||||||
|
if (resolved == null) resolved = "";
|
||||||
|
if (jsonValues.length() > 0) jsonValues.append(",");
|
||||||
|
jsonValues.append("\"").append(esc(token)).append("\":\"").append(esc(resolved)).append("\"");
|
||||||
|
}
|
||||||
|
String snapshot = jsonValues.toString();
|
||||||
|
if (snapshot.equals(lastPapiValues.get(p.getUniqueId()))) return;
|
||||||
|
lastPapiValues.put(p.getUniqueId(), snapshot);
|
||||||
|
String json = "{\"uuid\":\"" + p.getUniqueId() + "\",\"placeholders\":{" + snapshot + "}}";
|
||||||
|
httpExecutor.execute(() -> {
|
||||||
|
try { sendPost(statusApiUrl + "/player/papi", json); }
|
||||||
|
catch (Exception e) { getLogger().warning("[PAPI] Push fehlgeschlagen: " + e.getMessage()); }
|
||||||
|
});
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
} catch (Exception e) { getLogger().warning("[PAPI] Fehler: " + e.getMessage()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String esc(String s) {
|
||||||
|
if (s == null) return "";
|
||||||
|
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
private void pushTpsAsync(UUID uuid, double tps) {
|
private void pushTpsAsync(UUID uuid, double tps) {
|
||||||
httpExecutor.execute(() -> {
|
httpExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -379,24 +488,65 @@ public class StatusAPIBridge extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** TPS – Paper-API zuerst, dann Spigot-Reflection-Fallback */
|
/**
|
||||||
|
* Erkennt beim Start die Server-Version und setzt den internen Modus.
|
||||||
|
* Sichtbar im Server-Log als [StatusAPIBridge] Versions-Modus: ...
|
||||||
|
*/
|
||||||
|
private void detectMinecraftVersion() {
|
||||||
|
String bukkitVersion = Bukkit.getBukkitVersion(); // z.B. "1.21.1-R0.1-SNAPSHOT" oder "26.1.2-R0.1-SNAPSHOT"
|
||||||
|
// Alles ab 26.x gilt als "neuer Modus" ohne NMS-Fallback
|
||||||
|
try {
|
||||||
|
String major = bukkitVersion.split("\\.")[0];
|
||||||
|
int majorVersion = Integer.parseInt(major);
|
||||||
|
isLegacyMode = majorVersion < 26;
|
||||||
|
} catch (Exception e) {
|
||||||
|
isLegacyMode = true; // Fallback: sicherer Legacy-Modus
|
||||||
|
}
|
||||||
|
getLogger().info("Versions-Modus: "
|
||||||
|
+ (isLegacyMode ? "1.21.x-Modus (NMS-Fallback aktiv)" : "26.1.x-Modus (kein NMS-Fallback)")
|
||||||
|
+ " | BukkitVersion: " + bukkitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TPS auslesen – kompatibel mit Paper 1.21+, Spigot 1.21+, Java 17/21.
|
||||||
|
* Reihenfolge:
|
||||||
|
* 1. Paper-API: getTPS() direkt auf dem Server (sauberster Weg)
|
||||||
|
* 2. Spigot-Reflection: recentTps-Feld auf dem NMS-MinecraftServer
|
||||||
|
* 3. Fallback: 20.0
|
||||||
|
*/
|
||||||
private double getCurrentTps() {
|
private double getCurrentTps() {
|
||||||
|
// 1. Bevorzugt: Bukkit.getTPS() – funktioniert auf beiden Versionen
|
||||||
try {
|
try {
|
||||||
double[] tps = (double[]) Bukkit.getServer().getClass()
|
double[] tps = (double[]) Bukkit.getServer().getClass()
|
||||||
.getMethod("getTPS").invoke(Bukkit.getServer());
|
.getMethod("getTPS").invoke(Bukkit.getServer());
|
||||||
return Math.min(20.0, tps[0]);
|
if (tps != null && tps.length > 0) return Math.min(20.0, tps[0]);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
// 2. NMS-Reflection-Fallback – nur im 1.21.x-Modus
|
||||||
|
// Auf 26.1.x schlägt recentTps fehl → wird bewusst übersprungen
|
||||||
|
if (isLegacyMode) {
|
||||||
try {
|
try {
|
||||||
Object ms = Bukkit.getServer().getClass()
|
Object nmsServer = Bukkit.getServer().getClass()
|
||||||
.getMethod("getServer").invoke(Bukkit.getServer());
|
.getMethod("getServer").invoke(Bukkit.getServer());
|
||||||
double[] tps = (double[]) ms.getClass().getField("recentTps").get(ms);
|
for (String fieldName : new String[]{"recentTps", "tps"}) {
|
||||||
return Math.min(20.0, tps[0]);
|
try {
|
||||||
|
java.lang.reflect.Field f = nmsServer.getClass().getField(fieldName);
|
||||||
|
Object val = f.get(nmsServer);
|
||||||
|
if (val instanceof double[]) {
|
||||||
|
double[] tps = (double[]) val;
|
||||||
|
if (tps.length > 0) return Math.min(20.0, tps[0]);
|
||||||
|
}
|
||||||
|
} catch (NoSuchFieldException ignored2) {}
|
||||||
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
return 20.0;
|
return 20.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPost(String urlStr, String json) throws Exception {
|
private void sendPost(String urlStr, String json) throws Exception {
|
||||||
URL url = new URL(urlStr);
|
@SuppressWarnings("deprecation")
|
||||||
|
URL url = new java.net.URI(urlStr).toURL();
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("POST");
|
conn.setRequestMethod("POST");
|
||||||
conn.setDoOutput(true);
|
conn.setDoOutput(true);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
name: StatusAPIBridge
|
name: StatusAPIBridge
|
||||||
version: 1.0.0
|
version: 1.0.2
|
||||||
main: net.viper.statusapibridge.StatusAPIBridge
|
main: net.viper.statusapibridge.StatusAPIBridge
|
||||||
|
# 1.21 als niedrigste gemeinsame Basis – wird von 1.21.1 und 26.1.2 akzeptiert
|
||||||
api-version: 1.21
|
api-version: 1.21
|
||||||
description: Sendet Vault-Economy-Daten an die BungeeCord StatusAPI
|
description: Sendet Spielerdaten an die BungeeCord StatusAPI
|
||||||
authors: [Viper]
|
authors: [Viper]
|
||||||
softdepend: [Vault]
|
softdepend: [Vault, PlaceholderAPI]
|
||||||
|
|||||||
Reference in New Issue
Block a user