steel_protocol/packets/game/chat/
player.rs1use steel_macros::{ClientPacket, WriteTo};
2use steel_registry::packets::play::{C_DISGUISED_CHAT, C_PLAYER_CHAT};
3use steel_utils::{
4 codec::{BitSet, VarInt},
5 serial::PrefixedWrite,
6};
7use text_components::{TextComponent, resolving::TextResolutor};
8use uuid::Uuid;
9
10#[derive(Clone, Debug, WriteTo)]
13pub struct ChatTypeBound {
14 #[write(as = RegistryHolder)]
16 pub registry_id: i32,
17 pub sender_name: TextComponent,
19 pub target_name: Option<TextComponent>,
21}
22
23#[derive(ClientPacket, Clone, Debug)]
24#[packet_id(Play = C_PLAYER_CHAT)]
25pub struct CPlayerChat {
26 pub global_index: i32,
27 pub sender: Uuid,
28 pub index: i32,
29 pub message_signature: Option<Box<[u8]>>,
30 pub message: String,
31 pub timestamp: i64,
32 pub salt: i64,
33 pub previous_messages: Box<[PreviousMessage]>,
34 pub unsigned_content: Option<TextComponent>,
35 pub filter_type: FilterType,
36 pub chat_type: ChatTypeBound,
37}
38
39impl CPlayerChat {
40 #[expect(clippy::too_many_arguments)]
41 #[must_use]
42 pub const fn new(
43 global_index: i32,
44 sender: Uuid,
45 index: i32,
46 message_signature: Option<Box<[u8]>>,
47 message: String,
48 timestamp: i64,
49 salt: i64,
50 previous_messages: Box<[PreviousMessage]>,
51 unsigned_content: Option<TextComponent>,
52 filter_type: FilterType,
53 chat_type: ChatTypeBound,
54 ) -> Self {
55 Self {
56 global_index,
57 sender,
58 index,
59 message_signature,
60 message,
61 timestamp,
62 salt,
63 previous_messages,
64 unsigned_content,
65 filter_type,
66 chat_type,
67 }
68 }
69}
70
71impl steel_utils::serial::WriteTo for CPlayerChat {
72 fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
73 VarInt(self.global_index).write(writer)?;
74 self.sender.write(writer)?;
75 VarInt(self.index).write(writer)?;
76
77 match &self.message_signature {
78 Some(sig) => {
79 true.write(writer)?;
80 writer.write_all(sig)?;
81 }
82 None => false.write(writer)?,
83 }
84
85 self.message.write_prefixed::<VarInt>(writer)?;
86 self.timestamp.write(writer)?;
87 self.salt.write(writer)?;
88
89 VarInt(self.previous_messages.len() as i32).write(writer)?;
90 for msg in &self.previous_messages {
91 VarInt(msg.id).write(writer)?;
96 if msg.id == 0 {
98 if let Some(sig) = &msg.signature {
99 writer.write_all(sig)?;
100 } else {
101 return Err(std::io::Error::new(
103 std::io::ErrorKind::InvalidData,
104 "PreviousMessage with id=0 must have signature",
105 ));
106 }
107 }
108 }
109
110 match &self.unsigned_content {
111 Some(content) => {
112 true.write(writer)?;
113 content.write(writer)?;
114 }
115 None => false.write(writer)?,
116 }
117
118 VarInt(match self.filter_type {
119 FilterType::PassThrough => 0,
120 FilterType::FullyFiltered => 1,
121 FilterType::PartiallyFiltered(_) => 2,
122 })
123 .write(writer)?;
124
125 self.chat_type.write(writer)?;
126 Ok(())
127 }
128}
129
130#[derive(Clone, Debug)]
131pub struct PreviousMessage {
132 pub id: i32,
133 pub signature: Option<Box<[u8]>>,
134}
135
136#[derive(Clone, Debug)]
137pub enum FilterType {
138 PassThrough,
139 FullyFiltered,
140 PartiallyFiltered(BitSet),
141}
142
143#[derive(ClientPacket, WriteTo, Clone, Debug)]
146#[packet_id(Play = C_DISGUISED_CHAT)]
147pub struct CDisguisedChat {
148 pub message: TextComponent,
149 pub chat_type: ChatTypeBound,
150}
151
152impl CDisguisedChat {
153 pub fn new<T: TextResolutor>(
154 message: &TextComponent,
155 chat_type: ChatTypeBound,
156 player: &T,
157 ) -> Self {
158 Self {
159 message: message.resolve(player),
160 chat_type,
161 }
162 }
163}