diff --git a/build.gradle b/build.gradle index d6c733786..d8f431fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ dependencies { api 'io.netty:netty-codec:4.1.63.Final' api 'io.netty:netty-transport-native-epoll:4.1.63.Final:linux-x86_64' api 'io.netty:netty-transport-native-kqueue:4.1.63.Final:osx-x86_64' - api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.4.Final:linux-x86_64' + api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.5.Final:linux-x86_64' // https://mvnrepository.com/artifact/org.apache.commons/commons-text compile group: 'org.apache.commons', name: 'commons-text', version: '1.9' diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 8701fc915..5abf69e3e 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -626,7 +626,9 @@ public final class MinecraftServer { * Gets the consumer executed to show server-list data. * * @return the response data consumer + * @deprecated listen to the {@link net.minestom.server.event.server.ServerListPingEvent} instead */ + @Deprecated public static ResponseDataConsumer getResponseDataConsumer() { checkInitStatus(responseDataConsumer); return responseDataConsumer; @@ -752,15 +754,30 @@ public final class MinecraftServer { * @param port the server port * @param responseDataConsumer the response data consumer, can be null * @throws IllegalStateException if called before {@link #init()} or if the server is already running + * @deprecated use {@link #start(String, int)} and listen to the {@link net.minestom.server.event.server.ServerListPingEvent} event instead of ResponseDataConsumer */ + @Deprecated public void start(@NotNull String address, int port, @Nullable ResponseDataConsumer responseDataConsumer) { + MinecraftServer.responseDataConsumer = responseDataConsumer; + start(address, port); + } + + /** + * Starts the server. + *
+ * It should be called after {@link #init()} and probably your own initialization code. + * + * @param address the server address + * @param port the server port + * @throws IllegalStateException if called before {@link #init()} or if the server is already running + */ + public void start(@NotNull String address, int port) { Check.stateCondition(!initialized, "#start can only be called after #init"); Check.stateCondition(started, "The server is already started"); MinecraftServer.started = true; LOGGER.info("Starting Minestom server."); - MinecraftServer.responseDataConsumer = responseDataConsumer; updateManager.start(); @@ -788,17 +805,6 @@ public final class MinecraftServer { MinestomTerminal.start(); } - /** - * Starts the server. - * - * @param address the server address - * @param port the server port - * @see #start(String, int, ResponseDataConsumer) - */ - public void start(@NotNull String address, int port) { - start(address, port, null); - } - /** * Stops this server properly (saves if needed, kicking players, etc.) */ diff --git a/src/main/java/net/minestom/server/event/server/ServerListPingEvent.java b/src/main/java/net/minestom/server/event/server/ServerListPingEvent.java new file mode 100644 index 000000000..490a10d91 --- /dev/null +++ b/src/main/java/net/minestom/server/event/server/ServerListPingEvent.java @@ -0,0 +1,61 @@ +package net.minestom.server.event.server; + +import net.minestom.server.event.CancellableEvent; +import net.minestom.server.event.Event; +import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.ping.ResponseData; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a {@link PlayerConnection} sends a status packet, + * usually to display information on the server list. + */ +public class ServerListPingEvent extends Event implements CancellableEvent { + private boolean cancelled = false; + + private final ResponseData responseData; + private final PlayerConnection connection; + + + public ServerListPingEvent(ResponseData responseData, PlayerConnection connection) { + this.responseData = responseData; + this.connection = connection; + } + + /** + * ResponseData being returned. + * + * @return the response data being returned + */ + public @NotNull ResponseData getResponseData() { + return responseData; + } + + /** + * PlayerConnection of received packet. + * + * Note that the player has not joined the server at this time. + * + * @return the playerConnection. + */ + public @NotNull PlayerConnection getConnection() { + return connection; + } + + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancelling this event will cause you server to appear offline in the vanilla server list. + * + * @param cancel true if the event should be cancelled, false otherwise + */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java index efe60b9b8..e6f8cbe34 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java @@ -81,7 +81,6 @@ public class HandshakePacket implements ClientPreplayPacket { nettyPlayerConnection.UNSAFE_setBungeeUuid(playerUuid); nettyPlayerConnection.UNSAFE_setBungeeSkin(playerSkin); - } else { nettyPlayerConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING)); nettyPlayerConnection.disconnect(); @@ -93,6 +92,11 @@ public class HandshakePacket implements ClientPreplayPacket { } } + if (connection instanceof NettyPlayerConnection) { + // Give to the connection the server info that the client used + ((NettyPlayerConnection) connection).refreshServerInformation(serverAddress, serverPort, protocolVersion); + } + switch (nextState) { case 1: connection.setConnectionState(ConnectionState.STATUS); @@ -100,11 +104,6 @@ public class HandshakePacket implements ClientPreplayPacket { case 2: if (protocolVersion == MinecraftServer.PROTOCOL_VERSION) { connection.setConnectionState(ConnectionState.LOGIN); - - if (connection instanceof NettyPlayerConnection) { - // Give to the connection the server info that the client used - ((NettyPlayerConnection) connection).refreshServerInformation(serverAddress, serverPort); - } } else { // Incorrect client version connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT)); diff --git a/src/main/java/net/minestom/server/network/packet/client/status/StatusRequestPacket.java b/src/main/java/net/minestom/server/network/packet/client/status/StatusRequestPacket.java index 24da7a1f0..f7f0c9622 100644 --- a/src/main/java/net/minestom/server/network/packet/client/status/StatusRequestPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/status/StatusRequestPacket.java @@ -2,6 +2,7 @@ package net.minestom.server.network.packet.client.status; import net.kyori.adventure.text.Component; import net.minestom.server.MinecraftServer; +import net.minestom.server.event.server.ServerListPingEvent; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.packet.server.handshake.ResponsePacket; import net.minestom.server.network.player.PlayerConnection; @@ -29,10 +30,16 @@ public class StatusRequestPacket implements ClientPreplayPacket { if (consumer != null) consumer.accept(connection, responseData); - ResponsePacket responsePacket = new ResponsePacket(); - responsePacket.jsonResponse = responseData.build().toString(); + // Call event + ServerListPingEvent statusRequestEvent = new ServerListPingEvent(responseData, connection); + MinecraftServer.getGlobalEventHandler().callCancellableEvent(ServerListPingEvent.class, statusRequestEvent, + () -> { + ResponsePacket responsePacket = new ResponsePacket(); + responsePacket.jsonResponse = responseData.build().toString(); + + connection.sendPacket(responsePacket); + }); - connection.sendPacket(responsePacket); } @Override diff --git a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java index 70f570ed1..5d7167d70 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -51,6 +51,7 @@ public class NettyPlayerConnection extends PlayerConnection { private String loginUsername; private String serverAddress; private int serverPort; + private int protocolVersion; // Used for the login plugin request packet, to retrieve the channel from a message id, // cleared once the player enters the play state @@ -228,6 +229,7 @@ public class NettyPlayerConnection extends PlayerConnection { this.remoteAddress = remoteAddress; } + @Override public void disconnect() { this.channel.close(); @@ -266,8 +268,8 @@ public class NettyPlayerConnection extends PlayerConnection { * * @return the server address used */ - @Nullable - public String getServerAddress() { + @Override + public @Nullable String getServerAddress() { return serverAddress; } @@ -278,10 +280,36 @@ public class NettyPlayerConnection extends PlayerConnection { * * @return the server port used */ + @Override public int getServerPort() { return serverPort; } + /** + * Gets the protocol version of a client. + * + * @return protocol version of client. + */ + @Override + public int getProtocolVersion() { + return protocolVersion; + } + + + /** + * Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields. + * + * @param serverAddress the server address which the client used + * @param serverPort the server port which the client used + * @param protocolVersion the protocol version which the client used + */ + public void refreshServerInformation(@Nullable String serverAddress, int serverPort, int protocolVersion) { + this.serverAddress = serverAddress; + this.serverPort = serverPort; + this.protocolVersion = protocolVersion; + } + + @Nullable public UUID getBungeeUuid() { return bungeeUuid; @@ -339,16 +367,6 @@ public class NettyPlayerConnection extends PlayerConnection { } } - /** - * Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields. - * - * @param serverAddress the server address which the client used - * @param serverPort the server port which the client used - */ - public void refreshServerInformation(@Nullable String serverAddress, int serverPort) { - this.serverAddress = serverAddress; - this.serverPort = serverPort; - } @NotNull public ByteBuf getTickBuffer() { diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index e183c772a..730836dbd 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -117,6 +117,40 @@ public abstract class PlayerConnection { @NotNull public abstract SocketAddress getRemoteAddress(); + + /** + * Gets protocol version of client. + * + * @return the protocol version + */ + public int getProtocolVersion() { + return MinecraftServer.PROTOCOL_VERSION; + } + + /** + * Gets the server address that the client used to connect. + *
+ * WARNING: it is given by the client, it is possible for it to be wrong. + * + * @return the server address used + */ + public @Nullable String getServerAddress() { + return MinecraftServer.getNettyServer().getAddress(); + } + + + /** + * Gets the server port that the client used to connect. + *
+ * WARNING: it is given by the client, it is possible for it to be wrong. + * + * @return the server port used + */ + public int getServerPort() { + return MinecraftServer.getNettyServer().getPort(); + } + + /** * Forcing the player to disconnect. */ diff --git a/src/main/java/net/minestom/server/ping/ResponseData.java b/src/main/java/net/minestom/server/ping/ResponseData.java index 8c5f43ed0..6c2a03b6d 100644 --- a/src/main/java/net/minestom/server/ping/ResponseData.java +++ b/src/main/java/net/minestom/server/ping/ResponseData.java @@ -15,8 +15,7 @@ import java.util.UUID; /** * Represents the data sent to the player when refreshing the server list. * - *
Filled by {@link ResponseDataConsumer} and specified in {@link - * net.minestom.server.MinecraftServer#start(String, int, ResponseDataConsumer)}. + *
Edited by listening to the {@link net.minestom.server.event.server.ServerListPingEvent}.
*/
public class ResponseData {
private final List
+ * {@link UUID#randomUUID()} is used as the player's UUID.
+ *
+ * @param name The name of the player.
+ */
+ public void addPlayer(String name) {
+ PingPlayer pingPlayer = PingPlayer.of(name, UUID.randomUUID());
this.pingPlayers.add(pingPlayer);
}
@@ -123,6 +168,15 @@ public class ResponseData {
this.pingPlayers.clear();
}
+ /**
+ * Get the list of the response players.
+ *
+ * @return the list of the response players.
+ */
+ public List
+ * MUST start with "data:image/png;base64,"
*
* @param favicon The favicon for the response data.
*/
@@ -152,6 +217,16 @@ public class ResponseData {
this.favicon = favicon;
}
+ /**
+ * Get the response favicon.
+ *
+ * @return the response favicon.
+ */
+ public String getFavicon() {
+ return favicon;
+ }
+
+
/**
* Converts the response data into a {@link JsonObject}.
*
@@ -193,8 +268,26 @@ public class ResponseData {
/**
* Represents a player line in the server list hover.
*/
- private static class PingPlayer {
- private String name;
- private UUID uuid;
+ public static class PingPlayer {
+
+ private static @NotNull PingPlayer of(@NotNull String name, @NotNull UUID uuid) {
+ return new PingPlayer(name, uuid);
+ }
+
+ private final String name;
+ private final UUID uuid;
+
+ private PingPlayer(@NotNull String name, @NotNull UUID uuid) {
+ this.name = name;
+ this.uuid = uuid;
+ }
+
+ public @NotNull String getName() {
+ return name;
+ }
+
+ public @NotNull UUID getUuid() {
+ return uuid;
+ }
}
}
diff --git a/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java b/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java
index 251ef7179..21795703f 100644
--- a/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java
+++ b/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java
@@ -7,15 +7,18 @@ import net.minestom.server.network.player.PlayerConnection;
*
* Can be specified in {@link net.minestom.server.MinecraftServer#start(String, int,
* ResponseDataConsumer)}.
+ *
+ * @deprecated listen to the {@link net.minestom.server.event.server.ServerListPingEvent} instead
*/
@FunctionalInterface
+@Deprecated
public interface ResponseDataConsumer {
- /**
- * A method to fill the data of the response.
- *
- * @param playerConnection The player connection to which the response should be sent.
- * @param responseData The data for the response.
- */
- void accept(PlayerConnection playerConnection, ResponseData responseData);
+ /**
+ * A method to fill the data of the response.
+ *
+ * @param playerConnection The player connection to which the response should be sent.
+ * @param responseData The data for the response.
+ */
+ void accept(PlayerConnection playerConnection, ResponseData responseData);
}
diff --git a/src/main/java/net/minestom/server/terminal/MinestomTerminal.java b/src/main/java/net/minestom/server/terminal/MinestomTerminal.java
index b0123f899..cd5c1efe1 100644
--- a/src/main/java/net/minestom/server/terminal/MinestomTerminal.java
+++ b/src/main/java/net/minestom/server/terminal/MinestomTerminal.java
@@ -17,11 +17,11 @@ public class MinestomTerminal {
private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager();
private static final String PROMPT = "> ";
+ private static volatile Terminal terminal;
private static volatile boolean running = false;
@ApiStatus.Internal
public static void start() {
- Terminal terminal = null;
try {
terminal = TerminalBuilder.terminal();
} catch (IOException e) {
@@ -47,6 +47,13 @@ public class MinestomTerminal {
@ApiStatus.Internal
public static void stop() {
running = false;
+ if (terminal != null) {
+ try {
+ terminal.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
}
}
diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java
index ac677a688..850b93833 100644
--- a/src/test/java/demo/Main.java
+++ b/src/test/java/demo/Main.java
@@ -6,16 +6,21 @@ import demo.blocks.UpdatableBlockDemo;
import demo.commands.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandManager;
+import net.minestom.server.event.server.ServerListPingEvent;
import net.minestom.server.extras.optifine.OptifineSupport;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule;
+import net.minestom.server.ping.ResponseData;
import net.minestom.server.storage.StorageManager;
import net.minestom.server.storage.systems.FileStorageSystem;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
+import java.util.UUID;
+
public class Main {
@@ -59,6 +64,41 @@ public class Main {
MinecraftServer.getSchedulerManager().buildShutdownTask(() -> System.out.println("Good night")).schedule();
+ MinecraftServer.getGlobalEventHandler().addEventCallback(ServerListPingEvent.class, event -> {
+ ResponseData responseData = event.getResponseData();
+ responseData.setMaxPlayer(0);
+ responseData.setOnline(MinecraftServer.getConnectionManager().getOnlinePlayers().size());
+ responseData.addPlayer("The first line is separated from the others", UUID.randomUUID());
+ responseData.addPlayer("Could be a name, or a message", UUID.randomUUID());
+ responseData.addPlayer("IP test: " + event.getConnection().getRemoteAddress().toString(), UUID.randomUUID());
+ responseData.addPlayer("Use " + (char)0x00a7 + "7section characters", UUID.randomUUID());
+ responseData.addPlayer((char)0x00a7 + "7" + (char)0x00a7 + "ofor formatting" + (char)0x00a7 + "r: (" + (char)0x00a7 + "6char" + (char)0x00a7 + "r)" + (char)0x00a7 + "90x00a7", UUID.randomUUID());
+
+ responseData.addPlayer("Connection Info:");
+ String ip = event.getConnection().getServerAddress();
+ responseData.addPlayer((char)0x00a7 + "8- " + (char)0x00a7 +"7IP: " + (char)0x00a7 + "e" + (ip != null ? ip : "???"));
+ responseData.addPlayer((char)0x00a7 + "8- " + (char)0x00a7 +"7PORT: " + (char)0x00a7 + "e" + event.getConnection().getServerPort());
+ responseData.addPlayer((char)0x00a7 + "8- " + (char)0x00a7 +"7VERSION: " + (char)0x00a7 + "e" + event.getConnection().getProtocolVersion());
+
+ // Check if client supports RGB color
+ if (event.getConnection().getProtocolVersion() >= 713) { // Snapshot 20w17a
+ responseData.setDescription(Component.text("You can do ")
+ .append(Component.text("RGB", TextColor.color(0x66b3ff)))
+ .append(Component.text(" color here")));
+ } else {
+ responseData.setDescription(Component.text("You can do ")
+ .append(Component.text("RGB", NamedTextColor.nearestTo(TextColor.color(0x66b3ff))))
+ .append(Component.text(" color here,"))
+ .append(Component.newline())
+ .append(Component.text("if you are on 1.16 or up"))
+ );
+ }
+
+
+
+
+ });
+
PlayerInit.init();
OptifineSupport.enable();
@@ -68,7 +108,7 @@ public class Main {
//MojangAuth.init();
- minecraftServer.start("0.0.0.0", 25565, PlayerInit.getResponseDataConsumer());
+ minecraftServer.start("0.0.0.0", 25565);
//Runtime.getRuntime().addShutdownHook(new Thread(MinecraftServer::stopCleanly));
}
diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java
index d654fc1cc..043319dcc 100644
--- a/src/test/java/demo/PlayerInit.java
+++ b/src/test/java/demo/PlayerInit.java
@@ -4,6 +4,7 @@ import com.google.common.util.concurrent.AtomicDouble;
import demo.generator.ChunkGeneratorDemo;
import demo.generator.NoiseTestGenerator;
import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.chat.ColoredText;
@@ -292,14 +293,5 @@ public class PlayerInit {
});
}
- public static ResponseDataConsumer getResponseDataConsumer() {
- return (playerConnection, responseData) -> {
- responseData.setMaxPlayer(0);
- responseData.setOnline(MinecraftServer.getConnectionManager().getOnlinePlayers().size());
- responseData.addPlayer("A name", UUID.randomUUID());
- responseData.addPlayer("Could be some message", UUID.randomUUID());
- responseData.setDescription("IP test: " + playerConnection.getRemoteAddress());
- };
- }
}