package net.minecraft.server.chase; import com.mojang.logging.LogUtils; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.channels.ClosedByInterruptException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import javax.annotation.Nullable; import net.minecraft.Util; import net.minecraft.server.commands.ChaseCommand; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; public class ChaseServer { private static final Logger LOGGER = LogUtils.getLogger(); private final String serverBindAddress; private final int serverPort; private final PlayerList playerList; private final int broadcastIntervalMs; private volatile boolean wantsToRun; @Nullable private ServerSocket serverSocket; private final CopyOnWriteArrayList clientSockets = new CopyOnWriteArrayList<>(); public ChaseServer(String p_196032_, int p_196033_, PlayerList p_196034_, int p_196035_) { this.serverBindAddress = p_196032_; this.serverPort = p_196033_; this.playerList = p_196034_; this.broadcastIntervalMs = p_196035_; } public void start() throws IOException { if (this.serverSocket != null && !this.serverSocket.isClosed()) { LOGGER.warn("Remote control server was asked to start, but it is already running. Will ignore."); } else { this.wantsToRun = true; this.serverSocket = new ServerSocket(this.serverPort, 50, InetAddress.getByName(this.serverBindAddress)); Thread thread = new Thread(this::runAcceptor, "chase-server-acceptor"); thread.setDaemon(true); thread.start(); Thread thread1 = new Thread(this::runSender, "chase-server-sender"); thread1.setDaemon(true); thread1.start(); } } private void runSender() { ChaseServer.PlayerPosition chaseserver$playerposition = null; while (this.wantsToRun) { if (!this.clientSockets.isEmpty()) { ChaseServer.PlayerPosition chaseserver$playerposition1 = this.getPlayerPosition(); if (chaseserver$playerposition1 != null && !chaseserver$playerposition1.equals(chaseserver$playerposition)) { chaseserver$playerposition = chaseserver$playerposition1; byte[] abyte = chaseserver$playerposition1.format().getBytes(StandardCharsets.US_ASCII); for (Socket socket : this.clientSockets) { if (!socket.isClosed()) { Util.ioPool().execute(() -> { try { OutputStream outputstream = socket.getOutputStream(); outputstream.write(abyte); outputstream.flush(); } catch (IOException ioexception) { LOGGER.info("Remote control client socket got an IO exception and will be closed", (Throwable)ioexception); IOUtils.closeQuietly(socket); } }); } } } List list = this.clientSockets.stream().filter(Socket::isClosed).collect(Collectors.toList()); this.clientSockets.removeAll(list); } if (this.wantsToRun) { try { Thread.sleep(this.broadcastIntervalMs); } catch (InterruptedException interruptedexception) { } } } } public void stop() { this.wantsToRun = false; IOUtils.closeQuietly(this.serverSocket); this.serverSocket = null; } private void runAcceptor() { try { while (this.wantsToRun) { if (this.serverSocket != null) { LOGGER.info("Remote control server is listening for connections on port {}", this.serverPort); Socket socket = this.serverSocket.accept(); LOGGER.info("Remote control server received client connection on port {}", socket.getPort()); this.clientSockets.add(socket); } } } catch (ClosedByInterruptException closedbyinterruptexception) { if (this.wantsToRun) { LOGGER.info("Remote control server closed by interrupt"); } } catch (IOException ioexception) { if (this.wantsToRun) { LOGGER.error("Remote control server closed because of an IO exception", (Throwable)ioexception); } } finally { IOUtils.closeQuietly(this.serverSocket); } LOGGER.info("Remote control server is now stopped"); this.wantsToRun = false; } @Nullable private ChaseServer.PlayerPosition getPlayerPosition() { List list = this.playerList.getPlayers(); if (list.isEmpty()) { return null; } else { ServerPlayer serverplayer = list.get(0); String s = ChaseCommand.DIMENSION_NAMES.inverse().get(serverplayer.level().dimension()); return s == null ? null : new ChaseServer.PlayerPosition( s, serverplayer.getX(), serverplayer.getY(), serverplayer.getZ(), serverplayer.getYRot(), serverplayer.getXRot() ); } } record PlayerPosition(String dimensionName, double x, double y, double z, float yRot, float xRot) { String format() { return String.format( Locale.ROOT, "t %s %.2f %.2f %.2f %.2f %.2f\n", this.dimensionName, this.x, this.y, this.z, this.yRot, this.xRot ); } } }