WSS !!!
This commit is contained in:
@@ -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<UUID> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java
Normal file
43
bukkit/src/main/java/fr/redsavant/commands/MuteCommand.java
Normal file
@@ -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<UUID> mutedPlayers;
|
||||
|
||||
public MuteCommand(Set<UUID> 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 <player>");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<UUID, String> 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<UUID> mutedPlayers;
|
||||
|
||||
public ProximityTask(WebSocketClient wsClient, PlayerCodeManager codeManager, Set<UUID> mutedPlayers) {
|
||||
this.wsClient = wsClient;
|
||||
this.codeManager = codeManager;
|
||||
this.mutedPlayers = mutedPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!wsClient.isConnected()) return;
|
||||
|
||||
List<ProximityPayload.PlayerEntry> 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));
|
||||
}
|
||||
}
|
||||
1
bukkit/src/main/resources/config.yml
Normal file
1
bukkit/src/main/resources/config.yml
Normal file
@@ -0,0 +1 @@
|
||||
room-code: "CHANGE_ME"
|
||||
@@ -6,4 +6,9 @@ authors:
|
||||
- RedSavant
|
||||
- UnderScape (lagoon)
|
||||
api-version: '1.21'
|
||||
main: fr.redsavant.LagoonPlugin
|
||||
main: fr.redsavant.LagoonPlugin
|
||||
commands:
|
||||
proximity:
|
||||
description: Base command
|
||||
usage: /proximity mute <player>
|
||||
permission: lagoon.admin
|
||||
37
commun/src/main/java/fr/redsavant/ws/ProximityPayload.java
Normal file
37
commun/src/main/java/fr/redsavant/ws/ProximityPayload.java
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
94
commun/src/main/java/fr/redsavant/ws/WebSocketClient.java
Normal file
94
commun/src/main/java/fr/redsavant/ws/WebSocketClient.java
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user