diff --git a/libs.gradle b/libs.gradle index 33ed495..fc2589b 100644 --- a/libs.gradle +++ b/libs.gradle @@ -15,7 +15,8 @@ ext { lang3 : 'org.apache.commons:commons-lang3:3.11', netty : 'io.netty:netty-all:4.1.22.Final', reactor : 'io.projectreactor:reactor-core:3.4.5', - yaml : 'org.yaml:snakeyaml:1.28' + yaml : 'org.yaml:snakeyaml:1.28', + json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5' ] libs.logger = [ diff --git a/protocol/build.gradle b/protocol/build.gradle index 4677d8a..1a4110a 100644 --- a/protocol/build.gradle +++ b/protocol/build.gradle @@ -4,6 +4,7 @@ dependencies { implementation libs.netty implementation libs.reactor implementation libs.guava + implementation libs.json testImplementation libs.lang3 } diff --git a/protocol/src/main/java/mc/protocol/State.java b/protocol/src/main/java/mc/protocol/State.java index 0190377..b849adf 100644 --- a/protocol/src/main/java/mc/protocol/State.java +++ b/protocol/src/main/java/mc/protocol/State.java @@ -10,7 +10,7 @@ import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.server.DisconnectPacket; -import mc.protocol.packets.server.StatusServerResponsePacket; +import mc.protocol.packets.server.StatusServerResponse; import javax.annotation.Nullable; import java.util.Collections; @@ -31,7 +31,7 @@ public enum State { ), // server side Map.of( - StatusServerResponsePacket.class, 0x00, + StatusServerResponse.class, 0x00, PingPacket.class, 0x01 ) ), diff --git a/protocol/src/main/java/mc/protocol/model/ServerInfo.java b/protocol/src/main/java/mc/protocol/model/ServerInfo.java new file mode 100644 index 0000000..002d52b --- /dev/null +++ b/protocol/src/main/java/mc/protocol/model/ServerInfo.java @@ -0,0 +1,47 @@ +package mc.protocol.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.RequiredArgsConstructor; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Getter +@Setter +@ToString +public class ServerInfo { + + private final Version version = new Version(); + private final Players players = new Players(); + + private String description; + private String favicon; + + @Getter + @Setter + @ToString + public static class Version { + private String name; + private int protocol; + } + + @Getter + @Setter + @ToString + public static class Players { + private int max; + private int online; + private List sample; + } + + @RequiredArgsConstructor + @Getter + @EqualsAndHashCode + @ToString + public static class SamplePlayer { + private final String id; + private final String name; + } +} diff --git a/protocol/src/main/java/mc/protocol/model/lombok.config b/protocol/src/main/java/mc/protocol/model/lombok.config new file mode 100644 index 0000000..f4387a7 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/model/lombok.config @@ -0,0 +1 @@ +lombok.accessors.fluent=true \ No newline at end of file diff --git a/protocol/src/main/java/mc/protocol/packets/server/DisconnectPacket.java b/protocol/src/main/java/mc/protocol/packets/server/DisconnectPacket.java index dca7fef..c45891c 100644 --- a/protocol/src/main/java/mc/protocol/packets/server/DisconnectPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/server/DisconnectPacket.java @@ -1,5 +1,6 @@ package mc.protocol.packets.server; +import com.eclipsesource.json.Json; import lombok.Data; import mc.protocol.State; import mc.protocol.io.NetByteBuf; @@ -12,9 +13,16 @@ import mc.protocol.packets.ServerSidePacket; * *

Структура пакета

*
- * | FIELD  | TYPE | NOTES                            |
- * |--------|------|----------------------------------|
- * | Reason | Text | Причина отключения. Опционально. |
+ * | FIELD       | TYPE   | NOTES                            |
+ * |-------------|--------|----------------------------------|
+ * | JSON Reason | String | Причина отключения. Опционально. |
+ * 
+ * + *

Пример JSON Reason

+ *
+ * {
+ *     "text": "foo"
+ * }
  * 
* * @see Disconnect @@ -25,18 +33,11 @@ public class DisconnectPacket implements ServerSidePacket { /** * Причина отключения. - * - *

Пример:

- *
-	 * {
-	 *     "text": "foo"
-	 * }
-	 * 
*/ private String reason; @Override public void writeSelf(NetByteBuf netByteBuf) { - netByteBuf.writeString(reason); + netByteBuf.writeString(Json.object().add("text", reason).toString()); } } diff --git a/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponse.java b/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponse.java new file mode 100644 index 0000000..a238d20 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponse.java @@ -0,0 +1,92 @@ +package mc.protocol.packets.server; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.google.common.collect.Streams; +import lombok.Data; +import mc.protocol.io.NetByteBuf; +import mc.protocol.model.ServerInfo; +import mc.protocol.packets.ServerSidePacket; + +import java.util.stream.Collector; + +/** + * Status server packet, response. + * + *

Информация о сервере

+ * + *

Структура пакета + *

+ * | FIELD         | TYPE   | NOTES                                   |
+ * |---------------|--------|-----------------------------------------|
+ * | JSON Response | String | Информация о сервере в JSON формате [1] |
+ *
+ * [1] - Server List Ping: Response
+ * 
+ * + *

Пример JSON Response

+ *
+ * {
+ *     "version": {
+ *         "name": "1.8.7",
+ *         "protocol": 47
+ *     },
+ *     "players": {
+ *         "max": 100,
+ *         "online": 5,
+ *         "sample": [
+ *             {
+ *                 "name": "thinkofdeath",
+ *                 "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"
+ *             }
+ *         ]
+ *     },
+ *     "description": {
+ *         "text": "Hello world"
+ *     },
+ *     "favicon": "data:image/png;base64,<data>"
+ * }
+ * 
+ */ +@Data +public class StatusServerResponse implements ServerSidePacket { + + /** + * Информация о серере. + */ + private ServerInfo info; + + @Override + public void writeSelf(NetByteBuf netByteBuf) { + netByteBuf.writeString(Json.object() + .add("version", createVersionObj()) + .add("players", createPlayersObj()) + .add("description", Json.object().add("text", info.description())) + .toString()); + } + + private JsonObject createVersionObj() { + return Json.object() + .add("name", info.version().name()) + .add("protocol", info.version().protocol()); + } + + private JsonObject createPlayersObj() { + JsonArray sampleArr = info.players().sample().stream() + .map(samplePlayer -> Json.object() + .add("name", samplePlayer.name()) + .add("id", samplePlayer.id())) + .collect(Collector.of(Json::array, JsonArray::add, StatusServerResponse::jsonArrayAddAll)); + + return Json.object() + .add("max", info.players().max()) + .add("online", info.players().online()) + .add("sample", sampleArr); + } + + private static JsonArray jsonArrayAddAll(JsonArray jsonArrayTo, JsonArray jsonArrayFrom) { + Streams.stream(jsonArrayFrom).forEach(jsonArrayTo::add); + return jsonArrayTo; + } +} diff --git a/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponsePacket.java b/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponsePacket.java deleted file mode 100644 index fd6b4ef..0000000 --- a/protocol/src/main/java/mc/protocol/packets/server/StatusServerResponsePacket.java +++ /dev/null @@ -1,57 +0,0 @@ -package mc.protocol.packets.server; - -import lombok.Data; -import mc.protocol.io.NetByteBuf; -import mc.protocol.packets.ServerSidePacket; - -/** - * Status server packet, response. - * - *

Информация о сервере

- * - *

Структура пакета - *

- * | FIELD         | TYPE   | NOTES                                   |
- * |---------------|--------|-----------------------------------------|
- * | JSON Response | String | Информация о сервере в JSON формате [1] |
- *
- * [1] - Server List Ping: Response
- * 

- */ -@Data -public class StatusServerResponsePacket implements ServerSidePacket { - - /** - * Информация о серере в формате JSON - * - *

Пример

- *
-	 * {
-	 *     "version": {
-	 *         "name": "1.8.7",
-	 *         "protocol": 47
-	 *     },
-	 *     "players": {
-	 *         "max": 100,
-	 *         "online": 5,
-	 *         "sample": [
-	 *             {
-	 *                 "name": "thinkofdeath",
-	 *                 "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"
-	 *             }
-	 *         ]
-	 *     },
-	 *     "description": {
-	 *         "text": "Hello world"
-	 *     },
-	 *     "favicon": "data:image/png;base64,<data>"
-	 * }
-	 * 
- */ - private String info; - - @Override - public void writeSelf(NetByteBuf netByteBuf) { - netByteBuf.writeString(info); - } -} diff --git a/server/src/main/java/mc/server/Main.java b/server/src/main/java/mc/server/Main.java index 156aceb..0592fa1 100644 --- a/server/src/main/java/mc/server/Main.java +++ b/server/src/main/java/mc/server/Main.java @@ -3,18 +3,20 @@ package mc.server; import lombok.extern.slf4j.Slf4j; import mc.protocol.NettyServer; import mc.protocol.ProtocolConstant; +import mc.protocol.model.ServerInfo; import mc.protocol.packets.PingPacket; import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.server.DisconnectPacket; -import mc.protocol.packets.server.StatusServerResponsePacket; +import mc.protocol.packets.server.StatusServerResponse; import mc.server.config.Config; import mc.server.di.ConfigModule; import mc.server.di.DaggerServerComponent; import mc.server.di.ServerComponent; import java.nio.file.Paths; +import java.util.Collections; @Slf4j public class Main { @@ -48,21 +50,16 @@ public class Main { server.packetFlux(StatusServerRequestPacket.class) .doOnNext(channel -> log.info("{}", channel.getPacket())) .subscribe(channel -> { - StatusServerResponsePacket response = new StatusServerResponsePacket(); - response.setInfo("{\n" + - " \"version\": {\n" + - " \"name\": \"" + ProtocolConstant.PROTOCOL_NAME + "\",\n" + - " \"protocol\": " + ProtocolConstant.PROTOCOL_NUMBER + "\n" + - " },\n" + - " \"players\": {\n" + - " \"max\": " + config.players().maxOnlile() + ",\n" + - " \"online\": " + config.players().onlile() + ",\n" + - " \"sample\": []\n" + - " },\n" + - " \"description\": {\n" + - " \"text\": \"" + config.motd() + "\"\n" + - " }\n" + - "}"); + ServerInfo serverInfo = new ServerInfo(); + serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME); + serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER); + serverInfo.players().max(config.players().maxOnlile()); + serverInfo.players().online(config.players().onlile()); + serverInfo.players().sample(Collections.emptyList()); + serverInfo.description(config.motd()); + + StatusServerResponse response = new StatusServerResponse(); + response.setInfo(serverInfo); channel.getCtx().writeAndFlush(response); }); @@ -71,9 +68,7 @@ public class Main { .doOnNext(channel -> log.info("{}", channel.getPacket())) .subscribe(channel -> { DisconnectPacket disconnectPacket = new DisconnectPacket(); - disconnectPacket.setReason("{\n" + - " \"text\": \"Server is not available.\"\n" + - "}"); + disconnectPacket.setReason("Server is not available."); channel.getCtx().writeAndFlush(disconnectPacket).channel().disconnect(); });