From d9c58867862cd1ec18387edfa3a03157c7dcbac9 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Wed, 8 Jun 2022 16:12:51 +0100 Subject: [PATCH] Current work on migrating chat over to a registry --- .../connection/client/ConnectedPlayer.java | 4 +- .../proxy/connection/registry/ChatData.java | 194 ++++++++++++------ .../connection/registry/ChatRegistry.java | 58 ++++++ .../proxy/protocol/packet/JoinGame.java | 18 +- .../protocol/packet/chat/ChatBuilder.java | 47 ++++- 5 files changed, 243 insertions(+), 78 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatRegistry.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 33cae2f9a..d14629e66 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -318,7 +318,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, Component translated = translateMessage(message); connection.write(ChatBuilder.builder(this.getProtocolVersion()) - .component(translated).forIdentity(identity).toClient()); + .component(translated).forIdentity(identity).toClient(this)); } @Override @@ -332,7 +332,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, connection.write(ChatBuilder.builder(this.getProtocolVersion()) .component(translated).forIdentity(identity) .setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM) - .toClient()); + .toClient(this)); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatData.java index bb09edcad..7671ed853 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatData.java @@ -30,80 +30,148 @@ import org.jetbrains.annotations.NotNull; import java.util.List; */ +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.List; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.format.TextFormat; +import net.kyori.adventure.translation.Translatable; +import org.jetbrains.annotations.NotNull; + // TODO Implement public class ChatData { - /* - private static final ListBinaryTag EMPTY_LIST_TAG = ListBinaryTag.empty(); + private static final ListBinaryTag EMPTY_LIST_TAG = ListBinaryTag.empty(); + private final String identifier; + private final int id; - private final String identifier; - private final int id; - private final Map<> + public ChatData(int id, String identifier) { + this.id = id; + this.identifier = identifier; + } + /** + * Decodes an entry in the registry. + * + * @param binaryTag the binary tag to decode. + * @param version the version to decode for. + * @return The decoded ChatData + */ + public static ChatData decodeRegistryEntry(CompoundBinaryTag binaryTag, ProtocolVersion version) { + final String registryIdentifier = binaryTag.getString("name"); + final Integer id = binaryTag.getInt("id"); - public static class Decoration implements Translatable { + CompoundBinaryTag element = binaryTag.getCompound("element"); + ChatData decodedChatData = decodeElementCompound(element); + return decodedChatData.annotateWith(id, registryIdentifier); + } - private final List parameters; - private final List style; - private final String translationKey; + private ChatData annotateWith(Integer id, String registryIdentifier) { + return new ChatData(id, registryIdentifier); + } - public List getParameters() { - return parameters; - } + private static ChatData decodeElementCompound(CompoundBinaryTag element) { + System.out.println(element); + final CompoundBinaryTag chatCompund = element.getCompound("chat"); - public List getStyle() { - return style; - } - - @Override - public @NotNull String translationKey() { - return translationKey; - } - - public Decoration(List parameters, List style, String translationKey) { - this.parameters = Preconditions.checkNotNull(parameters); - this.style = Preconditions.checkNotNull(style); - this.translationKey = Preconditions.checkNotNull(translationKey); - Preconditions.checkArgument(translationKey.length() > 0); - } - - public static Decoration decodeRegistryEntry(CompoundBinaryTag toDecode) { - ImmutableList.Builder parameters = ImmutableList.builder(); - ListBinaryTag paramList = toDecode.getList("parameters", EMPTY_LIST_TAG); - if (paramList != EMPTY_LIST_TAG) { - paramList.forEach(binaryTag -> parameters.add(binaryTag.toString())); - } - - ImmutableList.Builder style = ImmutableList.builder(); - CompoundBinaryTag styleList = toDecode.getCompound("style"); - for (String key : styleList.keySet()) { - if ("color".equals(key)) { - NamedTextColor color = Preconditions.checkNotNull( - NamedTextColor.NAMES.value(styleList.getString(key))); - style.add(color); - } else { - // Key is a Style option instead - TextDecoration deco = TextDecoration.NAMES.value(key); - // This wouldn't be here if it wasn't applied, but here it goes anyway: - byte val = styleList.getByte(key); - if (val != 0) { - style.add(deco); - } - } - } - - String translationKey = toDecode.getString("translation_key"); - - return new Decoration(parameters.build(), style.build(), translationKey); - } - - public void encodeRegistryEntry(CompoundBinaryTag ) + Decoration chatDecoration = null; + final CompoundBinaryTag chatDecorationCompound = chatCompund.getCompound("decoration"); + if (chatDecorationCompound != CompoundBinaryTag.empty()) { + chatDecoration = Decoration.decodeRegistryEntry(chatDecorationCompound); } - public static enum Priority { - SYSTEM, - CHAT + return new ChatData(-1, "invalid"); + } + + public String getIdentifier() { + return identifier; + } + + public int getId() { + return id; + } + + + + + public static class Decoration implements Translatable { + + private final List parameters; + private final List style; + private final String translationKey; + + public List getParameters() { + return parameters; } -*/ + + public List getStyle() { + return style; + } + + @Override + public @NotNull String translationKey() { + return translationKey; + } + + /** + * Creates a Decoration with the associated data. + * @param parameters chat params + * @param style chat style + * @param translationKey translation key + */ + public Decoration(List parameters, List style, String translationKey) { + this.parameters = Preconditions.checkNotNull(parameters); + this.style = Preconditions.checkNotNull(style); + this.translationKey = Preconditions.checkNotNull(translationKey); + Preconditions.checkArgument(translationKey.length() > 0); + } + + /** + * Decodes a decoration entry. + * @param toDecode Compound Tag to decode + * @return the parsed Decoration entry. + */ + public static Decoration decodeRegistryEntry(CompoundBinaryTag toDecode) { + ImmutableList.Builder parameters = ImmutableList.builder(); + ListBinaryTag paramList = toDecode.getList("parameters", EMPTY_LIST_TAG); + if (paramList != EMPTY_LIST_TAG) { + paramList.forEach(binaryTag -> parameters.add(binaryTag.toString())); + } + + ImmutableList.Builder style = ImmutableList.builder(); + CompoundBinaryTag styleList = toDecode.getCompound("style"); + for (String key : styleList.keySet()) { + if ("color".equals(key)) { + NamedTextColor color = Preconditions.checkNotNull( + NamedTextColor.NAMES.value(styleList.getString(key))); + style.add(color); + } else { + // Key is a Style option instead + TextDecoration deco = TextDecoration.NAMES.value(key); + // This wouldn't be here if it wasn't applied, but here it goes anyway: + byte val = styleList.getByte(key); + if (val != 0) { + style.add(deco); + } + } + } + + String translationKey = toDecode.getString("translation_key"); + + return new Decoration(parameters.build(), style.build(), translationKey); + } + + public void encodeRegistryEntry(CompoundBinaryTag compoundBinaryTag) {} + + } + + public static enum Priority { + SYSTEM, + CHAT + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatRegistry.java new file mode 100644 index 000000000..f488bff94 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/ChatRegistry.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.connection.registry; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.velocitypowered.api.network.ProtocolVersion; + +import java.util.Map; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; + + +public final class ChatRegistry { + + private final Map registeredChatTypes; + + public ChatRegistry(ImmutableList chatDataImmutableList) { + registeredChatTypes = Maps.uniqueIndex(chatDataImmutableList, ChatData::getIdentifier); + } + + /** + * Decodes a CompoundTag storing a Chat Type Registry. + * + * @param compound The Compound to decode + * @param version Protocol version + * @return an ImmutableList of read ChatData + */ + public static ImmutableList fromGameData(ListBinaryTag compound, ProtocolVersion version) { + final ImmutableList.Builder builder = ImmutableList.builder(); + for (BinaryTag binaryTag : compound) { + if (binaryTag instanceof CompoundBinaryTag) { + builder.add(ChatData.decodeRegistryEntry((CompoundBinaryTag) binaryTag, version)); + } + } + return builder.build(); + } + + public BinaryTag build() { + return null; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index aba321564..2085d97f2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -17,9 +17,12 @@ package com.velocitypowered.proxy.protocol.packet; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.registry.ChatData; +import com.velocitypowered.proxy.connection.registry.ChatRegistry; import com.velocitypowered.proxy.connection.registry.DimensionData; import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.connection.registry.DimensionRegistry; @@ -32,6 +35,8 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class JoinGame implements MinecraftPacket { private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024); @@ -53,7 +58,7 @@ public class JoinGame implements MinecraftPacket { private CompoundBinaryTag biomeRegistry; // 1.16.2+ private int simulationDistance; // 1.18+ private @Nullable Pair lastDeathPosition; - private CompoundBinaryTag chatTypeRegistry; // placeholder, 1.19+ + private ChatRegistry chatTypeRegistry; // placeholder, 1.19+ public int getEntityId() { return entityId; @@ -183,11 +188,11 @@ public class JoinGame implements MinecraftPacket { this.lastDeathPosition = lastDeathPosition; } - public CompoundBinaryTag getChatTypeRegistry() { + public ChatRegistry getChatTypeRegistry() { return chatTypeRegistry; } - public void setChatTypeRegistry(CompoundBinaryTag chatTypeRegistry) { + public void setChatTypeRegistry(ChatRegistry chatTypeRegistry) { this.chatTypeRegistry = chatTypeRegistry; } @@ -274,9 +279,10 @@ public class JoinGame implements MinecraftPacket { .getList("value", BinaryTagTypes.COMPOUND); this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome"); if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { - this.chatTypeRegistry = registryContainer.getCompound("minecraft:chat_type"); + final ImmutableList chatDataList = ChatRegistry.fromGameData(registryContainer.getCompound("minecraft:chat_type").getList("value"), version); + this.chatTypeRegistry = new ChatRegistry(chatDataList); } else { - this.chatTypeRegistry = CompoundBinaryTag.empty(); + this.chatTypeRegistry = null; // TODO: Faux registry? } } else { dimensionRegistryContainer = registryContainer.getList("dimension", @@ -387,7 +393,7 @@ public class JoinGame implements MinecraftPacket { registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build()); registryContainer.put("minecraft:worldgen/biome", biomeRegistry); if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { - registryContainer.put("minecraft:chat_type", chatTypeRegistry); + registryContainer.put("minecraft:chat_type", chatTypeRegistry.build()); } } else { registryContainer.put("dimension", encodedDimensionRegistry); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java index d8732011b..b7cd79ab5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java @@ -21,15 +21,22 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.crypto.SignedChatCommand; import com.velocitypowered.proxy.crypto.SignedChatMessage; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; + import java.time.Instant; +import java.util.Objects; import java.util.UUID; + import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; public class ChatBuilder { @@ -125,10 +132,10 @@ public class ChatBuilder { * * @return The {@link MinecraftPacket} to send to the client. */ - public MinecraftPacket toClient() { + public MinecraftPacket toClient(ConnectedPlayer player) { // This is temporary UUID identity = sender == null ? (senderIdentity == null ? Identity.nil().uuid() - : senderIdentity.uuid()) : sender.getUniqueId(); + : senderIdentity.uuid()) : sender.getUniqueId(); Component msg = component == null ? Component.text(message) : component; if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { @@ -164,19 +171,45 @@ public class ChatBuilder { return chat; } - public static enum ChatType { - CHAT((byte) 0), - SYSTEM((byte) 1), - GAME_INFO((byte) 2); + public static class ChatType { + public static final ChatType CHAT = new ChatType((byte) 0, Key.key("minecraft", "chat")); + public static final ChatType SYSTEM = new ChatType((byte) 1, Key.key("minecraft", "system")); + public static final ChatType GAME_INFO = new ChatType((byte) 2, Key.key("minecraft", "game_info")); private final byte raw; + @NonNull + private final Key key; - ChatType(byte raw) { + ChatType(byte raw, @NonNull Key key) { + Preconditions.checkNotNull(key, "Key cannot be null!"); this.raw = raw; + this.key = key; } public byte getId() { return raw; } + + @NonNull + public Key getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ChatType chatType = (ChatType) o; + return raw == chatType.raw && key.equals(chatType.key); + } + + @Override + public int hashCode() { + return Objects.hash(raw, key); + } } }