diff --git a/StatusAPIBridge/pom.xml b/StatusAPIBridge/pom.xml new file mode 100644 index 0000000..2ef98af --- /dev/null +++ b/StatusAPIBridge/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + net.viper + StatusAPIBridge + 1.0.0 + jar + + + 17 + ${java.version} + ${java.version} + UTF-8 + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + vault-repo + https://nexus.hc.to/content/repositories/pub_releases/ + + + + + + + org.spigotmc + spigot-api + 1.21-R0.1-SNAPSHOT + provided + + + + net.milkbowl.vault + VaultAPI + 1.7 + provided + + + + + StatusAPIBridge + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + shade + + false + + + + + + + diff --git a/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java b/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java new file mode 100644 index 0000000..986d299 --- /dev/null +++ b/StatusAPIBridge/src/main/java/net/viper/statusapibridge/StatusAPIBridge.java @@ -0,0 +1,172 @@ +package net.viper.statusapibridge; + +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +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.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +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 final ExecutorService httpExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "StatusAPIBridge-HTTP"); + t.setDaemon(true); + return t; + }); + + @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)); + + if (!setupEconomy()) { + getLogger().warning("Vault/Economy nicht gefunden – Economy-Push deaktiviert."); + } else { + getLogger().info("Vault Economy gefunden: " + economy.getName()); + } + + Bukkit.getPluginManager().registerEvents(this, this); + Bukkit.getScheduler().runTaskTimer(this, this::pushChangedBalancesForOnlinePlayers, + liveSyncIntervalTicks, liveSyncIntervalTicks); + getLogger().info("StatusAPIBridge gestartet. Ziel: " + statusApiUrl); + } + + @Override + public void onDisable() { + httpExecutor.shutdownNow(); + } + + private boolean setupEconomy() { + if (getServer().getPluginManager().getPlugin("Vault") == null) return false; + RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); + if (rsp == null) return false; + economy = rsp.getProvider(); + return economy != null; + } + + // ── 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); + } + } + + @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); + } + 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()); + } + + // ── 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()); + } + }); + } + + // ── Economy-Push ─────────────────────────────────────────────────────────── + + public void pushEconomy(Player player) { + double balance = economy.getBalance(player); + pushEconomyAsync(player.getUniqueId(), player.getName(), balance); + } + + 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); + lastPushedBalance.put(uuid, balance); + } catch (Exception e) { + getLogger().warning("Economy-Push fehlgeschlagen fuer " + name + ": " + e.getMessage()); + } + }); + } + + 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); + } + } + } + + // ── HTTP ─────────────────────────────────────────────────────────────────── + + private void sendPost(String urlStr, String json) throws Exception { + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setConnectTimeout(3000); + conn.setReadTimeout(3000); + 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); + } + int code = conn.getResponseCode(); + 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("\"", "\\\""); + } +} diff --git a/StatusAPIBridge/src/main/resources/config.yml b/StatusAPIBridge/src/main/resources/config.yml new file mode 100644 index 0000000..be2cd64 --- /dev/null +++ b/StatusAPIBridge/src/main/resources/config.yml @@ -0,0 +1,8 @@ +# URL der BungeeCord StatusAPI (kein Slash am Ende) +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-interval-ticks: 20 diff --git a/StatusAPIBridge/src/main/resources/plugin.yml b/StatusAPIBridge/src/main/resources/plugin.yml new file mode 100644 index 0000000..694d0ae --- /dev/null +++ b/StatusAPIBridge/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +name: StatusAPIBridge +version: 1.0.0 +main: net.viper.statusapibridge.StatusAPIBridge +api-version: 1.21 +description: Sendet Vault-Economy-Daten an die BungeeCord StatusAPI +authors: [Viper] +softdepend: [Vault]