diff --git a/protocol/src/main/java/mc/protocol/State.java b/protocol/src/main/java/mc/protocol/State.java
index e46a2d1..f2bcbb3 100644
--- a/protocol/src/main/java/mc/protocol/State.java
+++ b/protocol/src/main/java/mc/protocol/State.java
@@ -57,6 +57,7 @@ public enum State {
PingPacket.class, 0x1F,
JoinGamePacket.class, 0x23,
ScoreboardDisplayPacket.class, 0x3B,
+ TeamsPacket.class, 0x44,
SpawnPositionPacket.class, 0x46,
ChunkDataPacket.class, 0x20,
PlayerAbilitiesPacket.class,0x2C,
diff --git a/protocol/src/main/java/mc/protocol/io/NetByteBuf.java b/protocol/src/main/java/mc/protocol/io/NetByteBuf.java
index b76ba17..f851077 100644
--- a/protocol/src/main/java/mc/protocol/io/NetByteBuf.java
+++ b/protocol/src/main/java/mc/protocol/io/NetByteBuf.java
@@ -99,6 +99,11 @@ public class NetByteBuf extends ByteBuf {
}
public void writeString(String string) {
+ if (string == null) {
+ writeVarInt(0);
+ return;
+ }
+
byte[] buf = string.getBytes(StandardCharsets.UTF_8);
if (buf.length > Short.MAX_VALUE) {
diff --git a/protocol/src/main/java/mc/protocol/packets/server/TeamsPacket.java b/protocol/src/main/java/mc/protocol/packets/server/TeamsPacket.java
new file mode 100644
index 0000000..8c223c1
--- /dev/null
+++ b/protocol/src/main/java/mc/protocol/packets/server/TeamsPacket.java
@@ -0,0 +1,119 @@
+package mc.protocol.packets.server;
+
+import lombok.Data;
+import mc.protocol.io.NetByteBuf;
+import mc.protocol.packets.ServerSidePacket;
+import mc.protocol.utils.TeamsCollisionRule;
+import mc.protocol.utils.TeamsMode;
+import mc.protocol.utils.TeamsNameTagVisibility;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Teams packet.
+ *
+ *
Структура пакета
+ *
+ * | FIELD | TYPE | NOTES |
+ * |------------ |-------------|-------------------------------------------------------|
+ * | Team Name | String (16) | Уникальное название команды (совместно со scoreboard) |
+ * | Mode | Byte | Режим. Определяет остальые поля пакета |
+ * | Data Fields | - | Определяется "Mode" |
+ *
+ *
+ * Варианты "Mode"
+ *
+ * | MODE | DATA FIELD | TYPE | NOTES |
+ * | VALUE | DESCRIPTION | | | |
+ * |-------|--------------------------|---------------------|----------------------|---------------------------------------------------|
+ * | 0 | create team | Team Display Name | String (32) | |
+ * | | | Team Prefix | String (16) | Отображается перед именем игроков текущей команды |
+ * | | | Team Suffix | String (16) | Отображается после имени игроков текущей команды |
+ * | | | Friendly Flags | Byte | Битовая маска: |
+ * | | | | | 0x01 - разрешён friendly fire |
+ * | | | | | 0x02 - могут видеть невидимок своей команды |
+ * | | | Name Tag Visibility | String (32) | фиксированные значения: |
+ * | | | | | - always |
+ * | | | | | - hideForOtherTeams |
+ * | | | | | - hideForOwnTeam |
+ * | | | | | - never |
+ * | | | Collision Rule | String (32) | фиксированные значения: |
+ * | | | | | - always |
+ * | | | | | - pushOtherTeams |
+ * | | | | | - pushOwnTeam |
+ * | | | | | - never |
+ * | | | Color | Byte | For colors, the same Chat colors (0-15). |
+ * | | | | | -1 indicates RESET/no color. |
+ * | | | Entity Count | VarInt | Количество элементов в поле "Entities" |
+ * | | | Entities | Array of String (40) | Уникальные идентификаторы участников команды. |
+ * | | | | | Для Игроков - это Имена |
+ * | | | | | Для любых других сущностей - это UUID |
+ * | 1 | remove team | - | - | удаление текущей команды |
+ * | 2 | update team info | Team Display Name | String (32) | |
+ * | | | Team Prefix | String (16) | (см. выше) |
+ * | | | Team Suffix | String (16) | (см. выше) |
+ * | | | Friendly Flags | Byte | (см. выше) |
+ * | | | Name Tag Visibility | String (32) | (см. выше) |
+ * | | | Collision Rule | String (32) | (см. выше) |
+ * | | | Color | Byte | (см. выше) |
+ * | 3 | add players to team | Entity Count | VarInt | (см. выше) |
+ * | | | Entities | Array of String (40) | (см. выше) |
+ * | 4 | remove players from team | Entity Count | VarInt | (см. выше) |
+ * | | | Entities | Array of String (40) | (см. выше) |
+ *
+ *
+ * @see Teams
+ */
+@Data
+public class TeamsPacket implements ServerSidePacket {
+
+ private final List members = new ArrayList<>();
+
+ private String name;
+ private TeamsMode mode;
+
+ private String displayName;
+ private String prefix;
+ private String suffix;
+ private TeamsNameTagVisibility nameTagVisibility;
+ private TeamsCollisionRule collisionRule;
+ private int color;
+
+ @Override
+ public void writeSelf(NetByteBuf netByteBuf) {
+ netByteBuf.writeString(this.name);
+ netByteBuf.writeByte(this.mode.getCode());
+
+ switch (this.mode) {
+ case CREATE:
+ netByteBuf.writeString(this.displayName);
+ netByteBuf.writeString(this.prefix);
+ netByteBuf.writeString(this.suffix);
+ netByteBuf.writeByte(0); // Friendly Flags
+ netByteBuf.writeString(this.nameTagVisibility.getCode());
+ netByteBuf.writeString(this.collisionRule.getCode());
+ netByteBuf.writeByte(this.color);
+ netByteBuf.writeVarInt(this.members.size());
+ this.members.forEach(netByteBuf::writeString);
+ break;
+ case UPDATE:
+ netByteBuf.writeString(this.displayName);
+ netByteBuf.writeString(this.prefix);
+ netByteBuf.writeString(this.suffix);
+ netByteBuf.writeByte(0); // Friendly Flags
+ netByteBuf.writeString(this.nameTagVisibility.getCode());
+ netByteBuf.writeString(this.collisionRule.getCode());
+ netByteBuf.writeByte(this.color);
+ break;
+ case ADD_MEMBER:
+ case REMOVE_MEMBER:
+ netByteBuf.writeVarInt(this.members.size());
+ this.members.forEach(netByteBuf::writeString);
+ break;
+ case REMOVE:
+ default:
+ break;
+ }
+ }
+}
diff --git a/protocol/src/main/java/mc/protocol/utils/TeamsCollisionRule.java b/protocol/src/main/java/mc/protocol/utils/TeamsCollisionRule.java
new file mode 100644
index 0000000..7a90d8e
--- /dev/null
+++ b/protocol/src/main/java/mc/protocol/utils/TeamsCollisionRule.java
@@ -0,0 +1,15 @@
+package mc.protocol.utils;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum TeamsCollisionRule {
+ ALWAYS("always"),
+ PUSH_OTHER_TEAMS("pushOtherTeams"),
+ PUSH_OWN_TEAM("pushOwnTeam"),
+ NEVER("never");
+
+ @Getter
+ private final String code;
+}
diff --git a/protocol/src/main/java/mc/protocol/utils/TeamsMode.java b/protocol/src/main/java/mc/protocol/utils/TeamsMode.java
new file mode 100644
index 0000000..5a0b427
--- /dev/null
+++ b/protocol/src/main/java/mc/protocol/utils/TeamsMode.java
@@ -0,0 +1,16 @@
+package mc.protocol.utils;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum TeamsMode {
+ CREATE(0),
+ REMOVE(1),
+ UPDATE(2),
+ ADD_MEMBER(3),
+ REMOVE_MEMBER(4);
+
+ @Getter
+ private final int code;
+}
diff --git a/protocol/src/main/java/mc/protocol/utils/TeamsNameTagVisibility.java b/protocol/src/main/java/mc/protocol/utils/TeamsNameTagVisibility.java
new file mode 100644
index 0000000..5b8f348
--- /dev/null
+++ b/protocol/src/main/java/mc/protocol/utils/TeamsNameTagVisibility.java
@@ -0,0 +1,15 @@
+package mc.protocol.utils;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum TeamsNameTagVisibility {
+ ALWAYS("always"),
+ HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
+ HIDE_FOR_OWN_TEAM("hideForOwnTeam"),
+ NEVER("never");
+
+ @Getter
+ private final String code;
+}
diff --git a/server/src/main/java/mc/server/PacketHandler.java b/server/src/main/java/mc/server/PacketHandler.java
index a7ec96d..466418d 100644
--- a/server/src/main/java/mc/server/PacketHandler.java
+++ b/server/src/main/java/mc/server/PacketHandler.java
@@ -13,10 +13,7 @@ import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket;
import mc.protocol.packets.server.*;
import mc.protocol.serializer.TextSerializer;
-import mc.protocol.utils.Difficulty;
-import mc.protocol.utils.GameMode;
-import mc.protocol.utils.LevelType;
-import mc.protocol.utils.ScoreboardPosition;
+import mc.protocol.utils.*;
import mc.server.config.Config;
import org.apache.commons.io.IOUtils;
@@ -69,15 +66,19 @@ public class PacketHandler {
}
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
+ UUID playerUuid = UUID.randomUUID();
+ int playerEid = random.nextInt();
+ String playerName = loginStartPacket.getName();
+
var loginSuccessPacket = new LoginSuccessPacket();
- loginSuccessPacket.setUuid(UUID.randomUUID());
- loginSuccessPacket.setName(loginStartPacket.getName());
+ loginSuccessPacket.setUuid(playerUuid);
+ loginSuccessPacket.setName(playerName);
context.sendNow(loginSuccessPacket);
context.setState(State.PLAY);
var joinGamePacket = new JoinGamePacket();
- joinGamePacket.setEntityId(random.nextInt());
+ joinGamePacket.setEntityId(playerEid);
joinGamePacket.setGameMode(GameMode.SPECTATOR);
joinGamePacket.setDimension(0/*Overworld*/);
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
@@ -126,11 +127,24 @@ public class PacketHandler {
// --- Эксперименты --- //
+ TeamsPacket teamsPacket = new TeamsPacket();
+ teamsPacket.setName("Score::List");
+ teamsPacket.setMode(TeamsMode.CREATE);
+ teamsPacket.setDisplayName(teamsPacket.getName());
+ teamsPacket.setNameTagVisibility(TeamsNameTagVisibility.NEVER);
+ teamsPacket.setCollisionRule(TeamsCollisionRule.NEVER);
+ teamsPacket.setColor(-1/*no color*/);
+ teamsPacket.getMembers().add(playerName);
+
+ context.send(teamsPacket);
+
ScoreboardDisplayPacket scoreboardDisplayPacket = new ScoreboardDisplayPacket();
scoreboardDisplayPacket.setPosition(ScoreboardPosition.LIST);
- scoreboardDisplayPacket.setName("Score::List");
+ scoreboardDisplayPacket.setName(teamsPacket.getName());
- context.sendNow(scoreboardDisplayPacket);
+ context.send(scoreboardDisplayPacket);
+
+ context.flushSending();
}
private static String faviconToBase64(Path iconPath) {