diff --git a/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java b/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java index 986d299..984e2a0 100644 --- a/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java +++ b/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java @@ -2,12 +2,15 @@ package net.viper.statusapibridge; import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; @@ -25,10 +28,17 @@ import java.util.concurrent.Executors; public class StatusAPIBridge extends JavaPlugin implements Listener { private Economy economy; - private String statusApiUrl; - private int pushDelayTicks; - private int liveSyncIntervalTicks; - private final Map lastPushedBalance = new ConcurrentHashMap<>(); + private String statusApiUrl; + private int pushDelayTicks; + private int liveSyncIntervalTicks; + private int scoreboardSyncIntervalTicks; + + private final Map lastPushedBalance = new ConcurrentHashMap<>(); + private final Map lastPushedHealth = new ConcurrentHashMap<>(); + private final Map lastPushedCompass = new ConcurrentHashMap<>(); + private final Map lastPushedWorld = new ConcurrentHashMap<>(); + private final Map lastPushedData = new ConcurrentHashMap<>(); + private final ExecutorService httpExecutor = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "StatusAPIBridge-HTTP"); t.setDaemon(true); @@ -38,9 +48,10 @@ public class StatusAPIBridge extends JavaPlugin implements Listener { @Override public void onEnable() { saveDefaultConfig(); - statusApiUrl = getConfig().getString("statusapi-url", "http://127.0.0.1:9191").trim(); - pushDelayTicks = getConfig().getInt("push-delay-ticks", 40); - liveSyncIntervalTicks = Math.max(20, getConfig().getInt("live-sync-interval-ticks", 20)); + statusApiUrl = getConfig().getString("statusapi-url", "http://127.0.0.1:9191").trim(); + pushDelayTicks = getConfig().getInt("push-delay-ticks", 40); + liveSyncIntervalTicks = Math.max(20, getConfig().getInt("live-sync-interval-ticks", 20)); + scoreboardSyncIntervalTicks = Math.max(20, getConfig().getInt("scoreboard-sync-interval-ticks", 20)); if (!setupEconomy()) { getLogger().warning("Vault/Economy nicht gefunden – Economy-Push deaktiviert."); @@ -51,6 +62,9 @@ public class StatusAPIBridge extends JavaPlugin implements Listener { Bukkit.getPluginManager().registerEvents(this, this); Bukkit.getScheduler().runTaskTimer(this, this::pushChangedBalancesForOnlinePlayers, liveSyncIntervalTicks, liveSyncIntervalTicks); + Bukkit.getScheduler().runTaskTimer(this, this::pushScoreboardData, + scoreboardSyncIntervalTicks, scoreboardSyncIntervalTicks); + getLogger().info("StatusAPIBridge gestartet. Ziel: " + statusApiUrl); } @@ -61,70 +75,98 @@ public class StatusAPIBridge extends JavaPlugin implements Listener { private boolean setupEconomy() { if (getServer().getPluginManager().getPlugin("Vault") == null) return false; - RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); + RegisteredServiceProvider rsp = + getServer().getServicesManager().getRegistration(Economy.class); if (rsp == null) return false; economy = rsp.getProvider(); return economy != null; } - // ── Events ───────────────────────────────────────────────────────────────── + // ── Events ──────────────────────────────────────────────────────────────── @EventHandler(priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent e) { Player player = e.getPlayer(); - // Welt sofort pushen - pushWorld(player.getUniqueId(), player.getName(), player.getWorld().getName()); - // Economy verzögert pushen - if (economy != null) { - Bukkit.getScheduler().runTaskLater(this, () -> { - if (player.isOnline()) pushEconomy(player); - }, pushDelayTicks); - } + Bukkit.getScheduler().runTaskLater(this, () -> { + if (!player.isOnline()) return; + if (economy != null) pushEconomy(player); + pushPlayerScoreboardData(player); + }, pushDelayTicks); } @EventHandler(priority = EventPriority.MONITOR) public void onQuit(PlayerQuitEvent e) { Player player = e.getPlayer(); - if (economy != null) { - double balance = economy.getBalance(player); - pushEconomyAsync(player.getUniqueId(), player.getName(), balance); + UUID id = player.getUniqueId(); + if (economy != null) pushEconomyAsync(id, player.getName(), economy.getBalance(player)); + lastPushedBalance.remove(id); + lastPushedHealth.remove(id); + lastPushedCompass.remove(id); + lastPushedWorld.remove(id); + lastPushedData.remove(id); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDamage(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player)) return; + Player player = (Player) e.getEntity(); + Bukkit.getScheduler().runTaskLater(this, + () -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onHeal(EntityRegainHealthEvent e) { + if (!(e.getEntity() instanceof Player)) return; + Player player = (Player) e.getEntity(); + Bukkit.getScheduler().runTaskLater(this, + () -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onMove(PlayerMoveEvent e) { + if (e.getFrom().getYaw() == e.getTo().getYaw()) return; + pushCompassIfChanged(e.getPlayer()); + } + + // ── Periodische Tasks ───────────────────────────────────────────────────── + + private void pushChangedBalancesForOnlinePlayers() { + if (economy == null) return; + for (Player player : Bukkit.getOnlinePlayers()) { + double current = economy.getBalance(player); + Double last = lastPushedBalance.get(player.getUniqueId()); + if (last == null || Math.abs(current - last) > 0.000001d) + pushEconomyAsync(player.getUniqueId(), player.getName(), current); } - lastPushedBalance.remove(player.getUniqueId()); } - @EventHandler(priority = EventPriority.MONITOR) - public void onWorldChange(PlayerChangedWorldEvent e) { - Player player = e.getPlayer(); - pushWorld(player.getUniqueId(), player.getName(), player.getWorld().getName()); + private void pushScoreboardData() { + double tps = getCurrentTps(); + for (Player player : Bukkit.getOnlinePlayers()) { + pushPlayerScoreboardData(player); + pushTpsAsync(player.getUniqueId(), tps); + } } - // ── World-Push ───────────────────────────────────────────────────────────── - - private void pushWorld(UUID uuid, String name, String world) { - httpExecutor.execute(() -> { - try { - String json = "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escape(name) - + "\",\"world\":\"" + escape(world) + "\"}"; - sendPost(statusApiUrl + "/player/world", json); - } catch (Exception ex) { - getLogger().warning("World-Push fehlgeschlagen fuer " + name + ": " + ex.getMessage()); - } - }); + private void pushPlayerScoreboardData(Player player) { + pushHealthIfChanged(player); + pushCompassIfChanged(player); + pushWorldIfChanged(player); + pushPlayerDataIfChanged(player); } - // ── Economy-Push ─────────────────────────────────────────────────────────── + // ── Push-Methoden ───────────────────────────────────────────────────────── public void pushEconomy(Player player) { - double balance = economy.getBalance(player); - pushEconomyAsync(player.getUniqueId(), player.getName(), balance); + pushEconomyAsync(player.getUniqueId(), player.getName(), economy.getBalance(player)); } private void pushEconomyAsync(UUID uuid, String name, double balance) { httpExecutor.execute(() -> { try { - String json = "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escape(name) - + "\",\"balance\":" + balance + "}"; - sendPost(statusApiUrl + "/economy/update", json); + sendPost(statusApiUrl + "/economy/update", + "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name) + + "\",\"balance\":" + balance + "}"); lastPushedBalance.put(uuid, balance); } catch (Exception e) { getLogger().warning("Economy-Push fehlgeschlagen fuer " + name + ": " + e.getMessage()); @@ -132,18 +174,127 @@ public class StatusAPIBridge extends JavaPlugin implements Listener { }); } - private void pushChangedBalancesForOnlinePlayers() { - if (economy == null) return; - for (Player player : Bukkit.getOnlinePlayers()) { - double current = economy.getBalance(player); - Double last = lastPushedBalance.get(player.getUniqueId()); - if (last == null || Math.abs(current - last) > 0.000001d) { - pushEconomyAsync(player.getUniqueId(), player.getName(), current); + private void pushHealthIfChanged(Player player) { + double health = player.getHealth(); + Double last = lastPushedHealth.get(player.getUniqueId()); + if (last != null && Math.abs(health - last) < 0.01) return; + lastPushedHealth.put(player.getUniqueId(), health); + UUID uuid = player.getUniqueId(); String name = player.getName(); + httpExecutor.execute(() -> { + try { + sendPost(statusApiUrl + "/scoreboard/health", + "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name) + + "\",\"health\":" + health + "}"); + } catch (Exception e) { + getLogger().warning("Health-Push fehlgeschlagen: " + e.getMessage()); } - } + }); } - // ── HTTP ─────────────────────────────────────────────────────────────────── + private void pushCompassIfChanged(Player player) { + // Rohen Yaw normalisieren auf 0..360 (0 = Süden, wie MC-Konvention) + float rawYaw = player.getLocation().getYaw(); + float normYaw = ((rawYaw % 360) + 360) % 360; + String yawStr = String.format(java.util.Locale.US, "%.1f", normYaw); + + // Nur senden wenn Änderung >= 0.5° – fein genug für 1-Grad-Slots + String lastStr = lastPushedCompass.get(player.getUniqueId()); + if (lastStr != null) { + try { + float lastYaw = Float.parseFloat(lastStr); + float diff = Math.abs(normYaw - lastYaw); + if (diff > 180) diff = 360 - diff; // kürzester Bogenweg + if (diff < 0.5f) return; + } catch (NumberFormatException ignored) {} + } + + lastPushedCompass.put(player.getUniqueId(), yawStr); + UUID uuid = player.getUniqueId(); String name = player.getName(); + httpExecutor.execute(() -> { + try { + sendPost(statusApiUrl + "/scoreboard/compass", + "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name) + + "\",\"compass\":\"" + yawStr + "\"}"); + } catch (Exception e) { + getLogger().warning("Compass-Push fehlgeschlagen: " + e.getMessage()); + } + }); + } + + private void pushWorldIfChanged(Player player) { + String world = player.getWorld().getName(); + if (world.equals(lastPushedWorld.get(player.getUniqueId()))) return; + lastPushedWorld.put(player.getUniqueId(), world); + UUID uuid = player.getUniqueId(); String name = player.getName(); + httpExecutor.execute(() -> { + try { + sendPost(statusApiUrl + "/player/world", + "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name) + + "\",\"world\":\"" + escapeName(world) + "\"}"); + } catch (Exception e) { + getLogger().warning("World-Push fehlgeschlagen: " + e.getMessage()); + } + }); + } + + private void pushPlayerDataIfChanged(Player player) { + int x = player.getLocation().getBlockX(); + int y = player.getLocation().getBlockY(); + int z = player.getLocation().getBlockZ(); + String gm = player.getGameMode().name(); + int exp= player.getLevel(); + int fd = player.getFoodLevel(); + double sp = player.getWalkSpeed(); + String wld= player.getWorld().getName(); + String key = x+","+y+","+z+","+gm+","+exp+","+fd+","+String.format("%.2f",sp)+","+wld; + if (key.equals(lastPushedData.get(player.getUniqueId()))) return; + lastPushedData.put(player.getUniqueId(), key); + UUID uuid = player.getUniqueId(); + String name = player.getName(); + httpExecutor.execute(() -> { + try { + sendPost(statusApiUrl + "/player/data", + "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name) + + "\",\"x\":" + x + + ",\"y\":" + y + + ",\"z\":" + z + + ",\"gamemode\":\"" + gm + "\"" + + ",\"exp\":" + exp + + ",\"food\":" + fd + + ",\"speed\":" + String.format(java.util.Locale.US, "%.4f", sp) + + ",\"world\":\"" + escapeName(wld) + "\"}"); + } catch (Exception e) { + getLogger().warning("PlayerData-Push fehlgeschlagen: " + e.getMessage()); + } + }); + } + + private void pushTpsAsync(UUID uuid, double tps) { + httpExecutor.execute(() -> { + try { + sendPost(statusApiUrl + "/scoreboard/tps", + "{\"uuid\":\"" + uuid + "\",\"tps\":" + tps + "}"); + } catch (Exception ignored) {} + }); + } + + // ── Hilfsmethoden ───────────────────────────────────────────────────────── + + /** TPS – Paper-API zuerst, dann Spigot-Reflection-Fallback */ + private double getCurrentTps() { + try { + double[] tps = (double[]) Bukkit.getServer().getClass() + .getMethod("getTPS").invoke(Bukkit.getServer()); + return Math.min(20.0, tps[0]); + } catch (Exception ignored) {} + try { + Object ms = Bukkit.getServer().getClass() + .getMethod("getServer").invoke(Bukkit.getServer()); + double[] tps = (double[]) ms.getClass().getField("recentTps").get(ms); + return Math.min(20.0, tps[0]); + } catch (Exception ignored) {} + return 20.0; + } private void sendPost(String urlStr, String json) throws Exception { URL url = new URL(urlStr); @@ -155,18 +306,15 @@ public class StatusAPIBridge extends JavaPlugin implements Listener { conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); byte[] body = json.getBytes(StandardCharsets.UTF_8); conn.setRequestProperty("Content-Length", String.valueOf(body.length)); - try (OutputStream os = conn.getOutputStream()) { - os.write(body); - } + try (OutputStream os = conn.getOutputStream()) { os.write(body); } int code = conn.getResponseCode(); - if (code != 200) { + if (code != 200) getLogger().warning("StatusAPI antwortete mit Code " + code + " fuer " + urlStr); - } conn.disconnect(); } - private String escape(String s) { - if (s == null) return ""; - return s.replace("\\", "\\\\").replace("\"", "\\\""); + private String escapeName(String name) { + if (name == null) return ""; + return name.replace("\\", "\\\\").replace("\"", "\\\""); } } diff --git a/StatusAPIBridge/src/main/resources/config.yml b/StatusAPIBridge/src/main/resources/config.yml index be2cd64..dc7104f 100644 --- a/StatusAPIBridge/src/main/resources/config.yml +++ b/StatusAPIBridge/src/main/resources/config.yml @@ -4,5 +4,9 @@ statusapi-url: "http://127.0.0.1:9191" # Wie viele Ticks nach dem Join wird die Balance gepusht? (20 Ticks = 1 Sekunde) push-delay-ticks: 40 -# Live-Sync Intervall fuer Economy-Updates waehrend der Spieler online ist (mind. 20 Ticks = 1 Sekunde) +# Live-Sync Intervall fuer Economy-Updates waehrend der Spieler online ist (mind. 20 Ticks) live-sync-interval-ticks: 20 + +# Sync-Intervall fuer Scoreboard-Daten (Health, Compass, TPS, World) in Ticks (mind. 20) +# Compass und Health werden zusaetzlich event-basiert aktualisiert. +scoreboard-sync-interval-ticks: 20