This commit is contained in:
RedSavant
2026-03-23 19:46:06 +01:00
parent 88547c6d5e
commit 41633b1bd5
9 changed files with 347 additions and 2 deletions

View File

@@ -0,0 +1,37 @@
package fr.redsavant.ws;
import java.util.List;
public class ProximityPayload {
public record PlayerEntry(double x, double y, double z, int layer, String code, boolean muted) {}
public static String buildPositionsJson(List<PlayerEntry> entries) {
StringBuilder sb = new StringBuilder();
sb.append("{\"type\":\"positions\",\"data\":[");
for (int i = 0; i < entries.size(); i++) {
PlayerEntry e = entries.get(i);
sb.append("{")
.append("\"x\":").append(e.x()).append(",")
.append("\"y\":").append(e.y()).append(",")
.append("\"z\":").append(e.z()).append(",")
.append("\"layer\":").append(e.layer()).append(",")
.append("\"code\":\"").append(e.code()).append("\"")
.append(",\"muted\":").append(e.muted())
.append("}");
if (i < entries.size() - 1) sb.append(",");
}
sb.append("]}");
return sb.toString();
}
public static int dimensionToLayer(String dimensionKey) {
return switch (dimensionKey) {
case "minecraft:the_nether" -> 1;
case "minecraft:the_end" -> 2;
default -> 0; // overworld
};
}
}

View File

@@ -0,0 +1,94 @@
package fr.redsavant.ws;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class WebSocketClient implements WebSocket.Listener {
private static final Logger LOGGER = Logger.getLogger(WebSocketClient.class.getName());
private final URI uri;
private final Consumer<String> onMessage;
private WebSocket socket;
private boolean connected = false;
private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
public WebSocketClient(URI uri, Consumer<String> onMessage) {
this.uri = uri;
this.onMessage = onMessage;
}
public void connect() {
HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(uri, this)
.thenAccept(ws -> {
this.socket = ws;
this.connected = true;
LOGGER.info("WebSocket connected : " + uri);
})
.exceptionally(ex -> {
LOGGER.warning("Failed to connect to WebSocket, retry in 5s : " + ex.getMessage());
scheduleReconnect();
return null;
});
}
public void send(String message) {
if (socket != null && connected) {
socket.sendText(message, true);
}
}
public void close() {
reconnectScheduler.shutdownNow();
if (socket != null) {
socket.sendClose(WebSocket.NORMAL_CLOSURE, "shutdown");
}
}
public boolean isConnected() {
return connected;
}
private void scheduleReconnect() {
reconnectScheduler.schedule(this::connect, 5, TimeUnit.SECONDS);
}
@Override
public void onOpen(WebSocket webSocket) {
connected = true;
LOGGER.info("WebSocket opened");
webSocket.request(1);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
onMessage.accept(data.toString());
webSocket.request(1);
return null;
}
@Override
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
connected = false;
LOGGER.warning("WebSocket closed (" + statusCode + "), re-connect in 5s...");
scheduleReconnect();
return null;
}
@Override
public void onError(WebSocket webSocket, Throwable error) {
connected = false;
LOGGER.warning("WebSocket error : " + error.getMessage());
scheduleReconnect();
}
}