From 41633b1bd563e5f8057a2ad74d8b82bc87231eef Mon Sep 17 00:00:00 2001 From: RedSavant Date: Mon, 23 Mar 2026 19:46:06 +0100 Subject: [PATCH] WSS !!! --- .../main/java/fr/redsavant/LagoonPlugin.java | 36 ++++++- .../fr/redsavant/commands/MuteCommand.java | 43 +++++++++ .../proximity/PlayerCodeManager.java | 37 ++++++++ .../proximity/ProximityListener.java | 38 ++++++++ .../fr/redsavant/proximity/ProximityTask.java | 56 +++++++++++ bukkit/src/main/resources/config.yml | 1 + bukkit/src/main/resources/plugin.yml | 7 +- .../fr/redsavant/ws/ProximityPayload.java | 37 ++++++++ .../java/fr/redsavant/ws/WebSocketClient.java | 94 +++++++++++++++++++ 9 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java create mode 100644 bukkit/src/main/java/fr/redsavant/proximity/PlayerCodeManager.java create mode 100644 bukkit/src/main/java/fr/redsavant/proximity/ProximityListener.java create mode 100644 bukkit/src/main/java/fr/redsavant/proximity/ProximityTask.java create mode 100644 bukkit/src/main/resources/config.yml create mode 100644 commun/src/main/java/fr/redsavant/ws/ProximityPayload.java create mode 100644 commun/src/main/java/fr/redsavant/ws/WebSocketClient.java diff --git a/bukkit/src/main/java/fr/redsavant/LagoonPlugin.java b/bukkit/src/main/java/fr/redsavant/LagoonPlugin.java index b806a81..a57d03f 100644 --- a/bukkit/src/main/java/fr/redsavant/LagoonPlugin.java +++ b/bukkit/src/main/java/fr/redsavant/LagoonPlugin.java @@ -1,12 +1,46 @@ package fr.redsavant; +import fr.redsavant.commands.MuteCommand; +import fr.redsavant.proximity.*; +import fr.redsavant.ws.WebSocketClient; + +import java.net.URI; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + public class LagoonPlugin extends BootstrapPlugin { + private WebSocketClient wsClient; + private ProximityTask proximityTask; + @Override protected void onStart() { + saveDefaultConfig(); + + String roomCode = getConfig().getString("room-code", "CHANGE_ME"); + URI wsUri = URI.create("wss://lagoon.under-scape.com/ws/rooms/" + roomCode + "/plugins/proximity"); + + PlayerCodeManager codeManager = new PlayerCodeManager(); + Set mutedPlayers = new HashSet<>(); + + wsClient = new WebSocketClient(wsUri, msg -> { + // future reponse de drewen + }); + wsClient.connect(); + + getServer().getPluginManager().registerEvents(new ProximityListener(codeManager), this); + getCommand("proximity").setExecutor(new MuteCommand(mutedPlayers)); + + proximityTask = new ProximityTask(wsClient, codeManager, mutedPlayers); + proximityTask.runTaskTimerAsynchronously(this, 20L, 60L); + + getLogger().info("LagoonPlugin enabled — room: " + roomCode); } @Override protected void onStop() { + if (proximityTask != null) proximityTask.cancel(); + if (wsClient != null) wsClient.close(); } -} +} \ No newline at end of file diff --git a/bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java b/bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java new file mode 100644 index 0000000..acb6a9e --- /dev/null +++ b/bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java @@ -0,0 +1,43 @@ +package fr.redsavant.commands; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.UUID; + +public class MuteCommand implements CommandExecutor { + + private final Set mutedPlayers; + + public MuteCommand(Set mutedPlayers) { + this.mutedPlayers = mutedPlayers; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length < 1) { + sender.sendMessage("§cUsage : /proximity mute "); + return true; + } + + Player target = Bukkit.getPlayerExact(args[0]); + if (target == null) { + sender.sendMessage("§cUnknow player"); + return true; + } + + UUID uuid = target.getUniqueId(); + if (mutedPlayers.contains(uuid)) { + mutedPlayers.remove(uuid); + sender.sendMessage("§a" + target.getName() + " has been unmuted on Lagoon"); + } else { + mutedPlayers.add(uuid); + sender.sendMessage("§e" + target.getName() + " has been muted on Lagoon"); + } + return true; + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/fr/redsavant/proximity/PlayerCodeManager.java b/bukkit/src/main/java/fr/redsavant/proximity/PlayerCodeManager.java new file mode 100644 index 0000000..3012212 --- /dev/null +++ b/bukkit/src/main/java/fr/redsavant/proximity/PlayerCodeManager.java @@ -0,0 +1,37 @@ +package fr.redsavant.proximity; + +import org.bukkit.entity.Player; + +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class PlayerCodeManager { + + private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // ambiguous chars removed + private static final int CODE_LENGTH = 6; + private static final SecureRandom RANDOM = new SecureRandom(); + + private final Map codes = new HashMap<>(); + + public String getOrCreate(Player player) { + return codes.computeIfAbsent(player.getUniqueId(), uuid -> generateCode()); + } + + public String get(UUID uuid) { + return codes.get(uuid); + } + + public void remove(UUID uuid) { + codes.remove(uuid); + } + + private String generateCode() { + StringBuilder sb = new StringBuilder(CODE_LENGTH); + for (int i = 0; i < CODE_LENGTH; i++) { + sb.append(CHARS.charAt(RANDOM.nextInt(CHARS.length()))); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/fr/redsavant/proximity/ProximityListener.java b/bukkit/src/main/java/fr/redsavant/proximity/ProximityListener.java new file mode 100644 index 0000000..eb851e1 --- /dev/null +++ b/bukkit/src/main/java/fr/redsavant/proximity/ProximityListener.java @@ -0,0 +1,38 @@ +package fr.redsavant.proximity; + +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class ProximityListener implements Listener { + + private final PlayerCodeManager codeManager; + + public ProximityListener(PlayerCodeManager codeManager) { + this.codeManager = codeManager; + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + String code = codeManager.getOrCreate(event.getPlayer()); + sendCodeMessage(event.getPlayer(), code); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + codeManager.remove(event.getPlayer().getUniqueId()); + } + + private void sendCodeMessage(org.bukkit.entity.Player player, String code) { + TextComponent msg = new TextComponent("§a[Lagoon] §fTon code de liaison : §b§l" + code); + msg.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, code)); + msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder("§7Clique pour copier").create())); + player.spigot().sendMessage(msg); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/fr/redsavant/proximity/ProximityTask.java b/bukkit/src/main/java/fr/redsavant/proximity/ProximityTask.java new file mode 100644 index 0000000..bf361d4 --- /dev/null +++ b/bukkit/src/main/java/fr/redsavant/proximity/ProximityTask.java @@ -0,0 +1,56 @@ +package fr.redsavant.proximity; + +import fr.redsavant.ws.ProximityPayload; +import fr.redsavant.ws.WebSocketClient; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class ProximityTask extends BukkitRunnable { + + private final WebSocketClient wsClient; + private final PlayerCodeManager codeManager; + private final Set mutedPlayers; + + public ProximityTask(WebSocketClient wsClient, PlayerCodeManager codeManager, Set mutedPlayers) { + this.wsClient = wsClient; + this.codeManager = codeManager; + this.mutedPlayers = mutedPlayers; + } + + @Override + public void run() { + if (!wsClient.isConnected()) return; + + List entries = new ArrayList<>(); + + for (Player player : Bukkit.getOnlinePlayers()) { + String code = codeManager.get(player.getUniqueId()); + if (code == null) continue; + + int layer = switch (player.getWorld().getEnvironment()) { + case NETHER -> 1; + case THE_END -> 2; + default -> 0; + }; + // LE WORLD ZEBI (spigot c'est nul X< ) + boolean muted = mutedPlayers.contains(player.getUniqueId()); + + entries.add(new ProximityPayload.PlayerEntry( + player.getLocation().getX(), + player.getLocation().getY(), + player.getLocation().getZ(), + layer, + code, + muted + )); + } + + wsClient.send(ProximityPayload.buildPositionsJson(entries)); + } +} \ No newline at end of file diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml new file mode 100644 index 0000000..73d63ea --- /dev/null +++ b/bukkit/src/main/resources/config.yml @@ -0,0 +1 @@ +room-code: "CHANGE_ME" \ No newline at end of file diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 5504cdf..5485e75 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -6,4 +6,9 @@ authors: - RedSavant - UnderScape (lagoon) api-version: '1.21' -main: fr.redsavant.LagoonPlugin \ No newline at end of file +main: fr.redsavant.LagoonPlugin +commands: + proximity: + description: Base command + usage: /proximity mute + permission: lagoon.admin \ No newline at end of file diff --git a/commun/src/main/java/fr/redsavant/ws/ProximityPayload.java b/commun/src/main/java/fr/redsavant/ws/ProximityPayload.java new file mode 100644 index 0000000..27760e9 --- /dev/null +++ b/commun/src/main/java/fr/redsavant/ws/ProximityPayload.java @@ -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 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 + }; + } +} \ No newline at end of file diff --git a/commun/src/main/java/fr/redsavant/ws/WebSocketClient.java b/commun/src/main/java/fr/redsavant/ws/WebSocketClient.java new file mode 100644 index 0000000..822620a --- /dev/null +++ b/commun/src/main/java/fr/redsavant/ws/WebSocketClient.java @@ -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 onMessage; + private WebSocket socket; + private boolean connected = false; + private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor(); + + public WebSocketClient(URI uri, Consumer 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(); + } +} \ No newline at end of file