azalea_protocol/packets/game/
c_player_chat.rs1use 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 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#[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#[derive(Clone, Debug, PartialEq)]
97pub struct MessageSignatureCache {
98 pub entries: Vec<Option<MessageSignature>>,
99}
100
101pub 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 #[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 #[must_use]
141 pub fn message(&self) -> FormattedText {
142 self.message_using_registries(&GUESSED_DEFAULT_REGISTRIES_FOR_CHAT)
143 }
144
145 #[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 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}