azalea_protocol/packets/game/
c_player_chat.rs

1use std::{
2    io::{self, Cursor, Write},
3    sync::LazyLock,
4};
5
6use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
7use azalea_chat::{
8    FormattedText,
9    translatable_component::{PrimitiveOrComponent, TranslatableComponent},
10};
11use azalea_core::{
12    bitset::BitSet,
13    data_registry::DataRegistryWithKey,
14    registry_holder::{RegistryHolder, RegistryType},
15};
16use azalea_crypto::signing::MessageSignature;
17use azalea_protocol_macros::ClientboundGamePacket;
18use azalea_registry::{
19    DataRegistryKey, Holder,
20    data::{ChatKind, ChatKindKey},
21    identifier::Identifier,
22};
23use simdnbt::owned::NbtCompound;
24use uuid::Uuid;
25
26#[derive(Clone, Debug, AzBuf, PartialEq, ClientboundGamePacket)]
27pub struct ClientboundPlayerChat {
28    #[var]
29    pub global_index: u32,
30    pub sender: Uuid,
31    #[var]
32    pub index: u32,
33    pub signature: Option<MessageSignature>,
34    pub body: PackedSignedMessageBody,
35    pub unsigned_content: Option<FormattedText>,
36    pub filter_mask: FilterMask,
37    pub chat_type: ChatTypeBound,
38}
39
40#[derive(Clone, Debug, PartialEq, AzBuf)]
41pub struct PackedSignedMessageBody {
42    // the error is here, for some reason it skipped a byte earlier and here
43    // it's reading `0` when it should be `11`
44    pub content: String,
45    pub timestamp: u64,
46    pub salt: u64,
47    pub last_seen: PackedLastSeenMessages,
48}
49
50#[derive(Clone, Debug, PartialEq, AzBuf)]
51pub struct PackedLastSeenMessages {
52    pub entries: Vec<PackedMessageSignature>,
53}
54
55/// Messages can be deleted by either their signature or message ID.
56#[derive(Clone, Debug, PartialEq)]
57pub enum PackedMessageSignature {
58    Signature(Box<MessageSignature>),
59    Id(u32),
60}
61
62#[derive(Clone, Debug, PartialEq, AzBuf)]
63pub enum FilterMask {
64    PassThrough,
65    FullyFiltered,
66    PartiallyFiltered(BitSet),
67}
68
69#[derive(Clone, Debug, PartialEq, AzBuf)]
70pub struct ChatTypeBound {
71    pub chat_type: Holder<ChatKind, DirectChatType>,
72    pub name: FormattedText,
73    pub target_name: Option<FormattedText>,
74}
75
76#[derive(Clone, Debug, PartialEq, AzBuf)]
77pub struct DirectChatType {
78    pub chat: ChatTypeDecoration,
79    pub narration: ChatTypeDecoration,
80}
81#[derive(Clone, Debug, PartialEq, AzBuf)]
82pub struct ChatTypeDecoration {
83    pub translation_key: String,
84    pub parameters: Vec<ChatTypeDecorationParameter>,
85    pub style: NbtCompound,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, AzBuf)]
89pub enum ChatTypeDecorationParameter {
90    Sender = 0,
91    Target = 1,
92    Content = 2,
93}
94
95// must be in Client
96#[derive(Clone, Debug, PartialEq)]
97pub struct MessageSignatureCache {
98    pub entries: Vec<Option<MessageSignature>>,
99}
100
101/// A `RegistryHolder` that only has the `chat_type` registry (without values),
102/// with the keys being in the default order for vanilla servers.
103///
104/// This is used when we call [`ClientboundPlayerChat::message`] without also
105/// passing registries.
106pub static GUESSED_DEFAULT_REGISTRIES_FOR_CHAT: LazyLock<RegistryHolder> =
107    LazyLock::new(|| RegistryHolder {
108        extra: [(
109            Identifier::new("chat_type"),
110            RegistryType {
111                map: ChatKindKey::ALL
112                    .iter()
113                    .map(|k| (k.clone().into_ident(), NbtCompound::new()))
114                    .collect(),
115            },
116        )]
117        .into_iter()
118        .collect(),
119        ..Default::default()
120    });
121
122impl ClientboundPlayerChat {
123    /// Returns the content of the message.
124    ///
125    /// If you want to get the [`FormattedText`] for the whole message including
126    /// the sender part, use [`ClientboundPlayerChat::message`].
127    #[must_use]
128    pub fn content(&self) -> FormattedText {
129        self.unsigned_content
130            .clone()
131            .unwrap_or_else(|| FormattedText::from(self.body.content.clone()))
132    }
133
134    /// Get the full message, including the sender part.
135    ///
136    /// Note that the returned message may be incorrect on servers that
137    /// customize the chat type registry. Consider using
138    /// [`Self::message_using_registries`] if you'd like to avoid that
139    /// problem.
140    #[must_use]
141    pub fn message(&self) -> FormattedText {
142        self.message_using_registries(&GUESSED_DEFAULT_REGISTRIES_FOR_CHAT)
143    }
144
145    /// Get the full message, including the sender part, while ensuring that the
146    /// message chat type is correct based on the server's registries.
147    ///
148    /// Also see [`Self::message`].
149    #[must_use]
150    pub fn message_using_registries(&self, registries: &RegistryHolder) -> FormattedText {
151        let sender = self.chat_type.name.clone();
152        let content = self.content();
153        let target = self.chat_type.target_name.clone();
154
155        let mut args = vec![
156            PrimitiveOrComponent::FormattedText(sender),
157            PrimitiveOrComponent::FormattedText(content),
158        ];
159        if let Some(target) = target {
160            args.push(PrimitiveOrComponent::FormattedText(target));
161        }
162
163        // TODO: implement chat type registry and apply the styles from it here
164        let translation_key = self.chat_type.translation_key(registries);
165        let component = TranslatableComponent::new(translation_key.to_owned(), args);
166
167        FormattedText::Translatable(component)
168    }
169}
170
171impl ChatTypeBound {
172    pub fn translation_key(&self, registries: &RegistryHolder) -> &str {
173        match &self.chat_type {
174            Holder::Reference(r) => r
175                .key(registries)
176                .map(|r| r.chat_translation_key())
177                .unwrap_or("chat.type.text"),
178            Holder::Direct(d) => d.chat.translation_key.as_str(),
179        }
180    }
181}
182
183impl AzaleaRead for PackedMessageSignature {
184    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
185        let id = u32::azalea_read_var(buf)?;
186        if id == 0 {
187            let full_signature = MessageSignature::azalea_read(buf)?;
188
189            Ok(PackedMessageSignature::Signature(Box::new(full_signature)))
190        } else {
191            Ok(PackedMessageSignature::Id(id - 1))
192        }
193    }
194}
195impl AzaleaWrite for PackedMessageSignature {
196    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
197        match self {
198            PackedMessageSignature::Signature(full_signature) => {
199                0u32.azalea_write_var(buf)?;
200                full_signature.azalea_write(buf)?;
201            }
202            PackedMessageSignature::Id(id) => {
203                (id + 1).azalea_write_var(buf)?;
204            }
205        }
206        Ok(())
207    }
208}