diff --git a/src/main/java/net/minestom/server/event/server/ClientPingServerEvent.java b/src/main/java/net/minestom/server/event/server/ClientPingServerEvent.java new file mode 100644 index 000000000..8571001c5 --- /dev/null +++ b/src/main/java/net/minestom/server/event/server/ClientPingServerEvent.java @@ -0,0 +1,128 @@ +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.utils.time.TimeUnit; +import net.minestom.server.utils.time.UpdateOption; +import org.jetbrains.annotations.NotNull; + + +/** + * Called when a {@link PlayerConnection} sends a ping packet, + * usually after the status packet. Only used in versions since the netty rewrite; 1.7+ + * + * @see ServerListPingEvent + */ +public class ClientPingServerEvent extends Event implements CancellableEvent { + private static final UpdateOption DEFAULT_DELAY = new UpdateOption(0, TimeUnit.MILLISECOND); + + private final PlayerConnection connection; + private long payload; + + private boolean cancelled = false; + private UpdateOption delay; + + /** + * Creates a new client ping server event with 0 delay + * + * @param connection the player connection + * @param payload the payload the client sent + */ + public ClientPingServerEvent(@NotNull PlayerConnection connection, long payload) { + this.connection = connection; + this.payload = payload; + this.delay = DEFAULT_DELAY; + } + + /** + * Creates a new client ping server event with 0 delay + * + * @param connection the player connection + * @param payload the payload the client sent + */ + public ClientPingServerEvent(@NotNull PlayerConnection connection, long payload, UpdateOption delay) { + this.connection = connection; + this.payload = payload; + this.delay = delay; + } + + + /** + * PlayerConnection of received packet. Note that the player has not joined the server + * at this time. + * + * @return the connection. + */ + public @NotNull PlayerConnection getConnection() { + return connection; + } + + /** + * Payload of received packet. May be any number; vanilla uses a system dependant time value. + * + * @return the payload + */ + public long getPayload() { + return payload; + } + + /** + * Sets the payload to respond with. + * + * Note: This should be the same as the client sent, however vanilla 1.17 seems to be OK with a different payload. + * @param payload the payload + */ + public void setPayload(long payload) { + this.payload = payload; + } + + /** + * Gets the delay until minestom will send the ping response packet. + * + * @return the delay + */ + public @NotNull UpdateOption getDelay() { + return delay; + } + + /** + * Adds to the delay until minestom will send the ping response packet. + * + * @param delay the delay + */ + public void addDelay(@NotNull UpdateOption delay) { + this.delay = new UpdateOption(this.delay.toMilliseconds() + delay.toMilliseconds(), TimeUnit.MILLISECOND); + } + + /** + * Sets the delay until minestom will send the ping response packet. + * + * @param delay the delay + */ + public void setDelay(@NotNull UpdateOption delay) { + this.delay = delay; + } + + /** + * Clears the delay until minestom will send the ping response packet. + */ + public void noDelay() { + this.delay = DEFAULT_DELAY; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancelling this event will cause the 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/status/PingPacket.java b/src/main/java/net/minestom/server/network/packet/client/status/PingPacket.java index bd160a854..6a8b4599d 100644 --- a/src/main/java/net/minestom/server/network/packet/client/status/PingPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/status/PingPacket.java @@ -1,5 +1,8 @@ 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.ClientPingServerEvent; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.packet.server.status.PongPacket; import net.minestom.server.network.player.PlayerConnection; @@ -15,9 +18,22 @@ public class PingPacket implements ClientPreplayPacket { @Override public void process(@NotNull PlayerConnection connection) { - PongPacket pongPacket = new PongPacket(number); - connection.sendPacket(pongPacket); - connection.disconnect(); + final ClientPingServerEvent clientPingEvent = new ClientPingServerEvent(connection, number); + MinecraftServer.getGlobalEventHandler().callEvent(ClientPingServerEvent.class, clientPingEvent); + + if (clientPingEvent.isCancelled()) { + connection.disconnect(); + } else { + if (clientPingEvent.getDelay().toMilliseconds() == 0) { + connection.sendPacket(new PongPacket(clientPingEvent.getPayload())); + connection.disconnect(); + } else { + MinecraftServer.getSchedulerManager().buildTask(() -> { + connection.sendPacket(new PongPacket(clientPingEvent.getPayload())); + connection.disconnect(); + }).delay(clientPingEvent.getDelay()).schedule(); + } + } } @Override diff --git a/src/main/java/net/minestom/server/ping/ResponseData.java b/src/main/java/net/minestom/server/ping/ResponseData.java index f4df9ab57..d30cb5966 100644 --- a/src/main/java/net/minestom/server/ping/ResponseData.java +++ b/src/main/java/net/minestom/server/ping/ResponseData.java @@ -29,6 +29,7 @@ public class ResponseData { private int online; private Component description; private String favicon; + private boolean playersHidden; /** * Constructs a new {@link ResponseData}. @@ -41,6 +42,7 @@ public class ResponseData { this.maxPlayer = this.online + 1; this.description = DEFAULT_DESCRIPTION; this.favicon = ""; + this.playersHidden = false; } /** @@ -294,6 +296,25 @@ public class ResponseData { return this.entries; } + /** + * Sets whether the players are hidden or not. + * In the vanilla client, `???` will be displayed where the online and maximum players would be. + * + * @param playersHidden if the players are hidden + */ + public void setPlayersHidden(boolean playersHidden) { + this.playersHidden = playersHidden; + } + + /** + * Returns if the players are hidden or not. + * + * @return if the players are hidden + */ + public boolean arePlayersHidden() { + return playersHidden; + } + /** * Converts the response data into a {@link JsonObject}. * diff --git a/src/main/java/net/minestom/server/ping/ServerListPingType.java b/src/main/java/net/minestom/server/ping/ServerListPingType.java index 05f13f47a..3da6e89f0 100644 --- a/src/main/java/net/minestom/server/ping/ServerListPingType.java +++ b/src/main/java/net/minestom/server/ping/ServerListPingType.java @@ -109,22 +109,25 @@ public enum ServerListPingType { versionObject.addProperty("name", data.getVersion()); versionObject.addProperty("protocol", data.getProtocol()); - // players info - final JsonObject playersObject = new JsonObject(); - playersObject.addProperty("max", data.getMaxPlayer()); - playersObject.addProperty("online", data.getOnline()); + JsonObject playersObject = null; + if (!data.arePlayersHidden()) { + // players info + playersObject = new JsonObject(); + playersObject.addProperty("max", data.getMaxPlayer()); + playersObject.addProperty("online", data.getOnline()); - // individual players - final JsonArray sampleArray = new JsonArray(); - for (NamedAndIdentified entry : data.getEntries()) { - JsonObject playerObject = new JsonObject(); - playerObject.addProperty("name", SECTION.serialize(entry.getName())); - playerObject.addProperty("id", entry.getUuid().toString()); - sampleArray.add(playerObject); + // individual players + final JsonArray sampleArray = new JsonArray(); + for (NamedAndIdentified entry : data.getEntries()) { + JsonObject playerObject = new JsonObject(); + playerObject.addProperty("name", SECTION.serialize(entry.getName())); + playerObject.addProperty("id", entry.getUuid().toString()); + sampleArray.add(playerObject); + } + + playersObject.add("sample", sampleArray); } - playersObject.add("sample", sampleArray); - final JsonObject jsonObject = new JsonObject(); jsonObject.add("version", versionObject); jsonObject.add("players", playersObject); diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index 184696bcb..c3cec28cc 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -6,6 +6,7 @@ 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.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.minestom.server.MinecraftServer; @@ -86,6 +87,9 @@ public class Main { .append(Component.text(" VERSION: ", NamedTextColor.GRAY)) .append(Component.text(event.getConnection().getProtocolVersion())))); } + responseData.addEntry(NamedAndIdentified.named(Component.text("Time", NamedTextColor.YELLOW) + .append(Component.text(": ", NamedTextColor.GRAY)) + .append(Component.text(System.currentTimeMillis(), Style.style(TextDecoration.ITALIC))))); // components will be converted the legacy section sign format so they are displayed in the client responseData.addEntry(NamedAndIdentified.named(Component.text("You can use ").append(Component.text("styling too!", NamedTextColor.RED, TextDecoration.BOLD)))); @@ -93,6 +97,7 @@ public class Main { // the data will be automatically converted to the correct format on response, so you can do RGB and it'll be downsampled! // on legacy versions, colors will be converted to the section format so it'll work there too responseData.setDescription(Component.text("This is a Minestom Server", TextColor.color(0x66b3ff))); + //responseData.setPlayersHidden(true); }); PlayerInit.init();