package net.scopenet.integration; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** Sends live player positions to the panel for the in-app BlueMap territory view. */ public final class LiveMapSync implements AutoCloseable { public record Player(String uuid, String name, String dimension, double x, double y, double z, float yaw, float pitch) {} private final PanelClient client; private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(task -> { Thread thread = new Thread(task, "scopenet-positions"); thread.setDaemon(true); return thread; }); private volatile List players = List.of(); private volatile boolean changed; private volatile boolean closed; private long lastSent; public LiveMapSync(PanelClient client) { this.client = client; } public void start() { worker.scheduleWithFixedDelay(this::sendSafely, 1, 1, TimeUnit.SECONDS); } public void offerPlayers(List next) { if (!next.equals(players)) { players = List.copyOf(next); changed = true; } } private void sendSafely() { if (closed) return; long now = System.nanoTime(); if (!changed && now - lastSent < TimeUnit.SECONDS.toNanos(10)) return; JsonArray list = new JsonArray(); for (Player p : players) { JsonObject entry = new JsonObject(); entry.addProperty("uuid", p.uuid()); entry.addProperty("name", p.name()); entry.addProperty("dimension", p.dimension()); entry.addProperty("x", p.x()); entry.addProperty("y", p.y()); entry.addProperty("z", p.z()); entry.addProperty("yaw", p.yaw()); entry.addProperty("pitch", p.pitch()); list.add(entry); } JsonObject body = new JsonObject(); body.add("players", list); try { client.post("livemap/players", body); changed = false; lastSent = now; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception ignored) { // Keep the latest snapshot and retry on the next interval. } } @Override public void close() { closed = true; worker.shutdownNow(); } }