azalea_client/plugins/chat/
mod.rs1pub mod handler;
4
5use std::sync::Arc;
6
7use azalea_chat::FormattedText;
8use azalea_protocol::packets::game::{
9 c_disguised_chat::ClientboundDisguisedChat, c_player_chat::ClientboundPlayerChat,
10 c_system_chat::ClientboundSystemChat,
11};
12use bevy_app::{App, Plugin, Update};
13use bevy_ecs::{
14 entity::Entity,
15 event::{EventReader, EventWriter},
16 prelude::Event,
17 schedule::IntoSystemConfigs,
18};
19use handler::{SendChatKindEvent, handle_send_chat_kind_event};
20use uuid::Uuid;
21
22use crate::client::Client;
23
24pub struct ChatPlugin;
25impl Plugin for ChatPlugin {
26 fn build(&self, app: &mut App) {
27 app.add_event::<SendChatEvent>()
28 .add_event::<SendChatKindEvent>()
29 .add_event::<ChatReceivedEvent>()
30 .add_systems(
31 Update,
32 (handle_send_chat_event, handle_send_chat_kind_event).chain(),
33 );
34 }
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub enum ChatPacket {
40 System(Arc<ClientboundSystemChat>),
41 Player(Arc<ClientboundPlayerChat>),
42 Disguised(Arc<ClientboundDisguisedChat>),
43}
44
45macro_rules! regex {
46 ($re:literal $(,)?) => {{
47 static RE: std::sync::LazyLock<regex::Regex> =
48 std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap());
49 &RE
50 }};
51}
52
53impl ChatPacket {
54 pub fn message(&self) -> FormattedText {
56 match self {
57 ChatPacket::System(p) => p.content.clone(),
58 ChatPacket::Player(p) => p.message(),
59 ChatPacket::Disguised(p) => p.message(),
60 }
61 }
62
63 pub fn split_sender_and_content(&self) -> (Option<String>, String) {
68 match self {
69 ChatPacket::System(p) => {
70 let message = p.content.to_string();
71 if p.overlay {
73 return (None, message);
74 }
75 if let Some(m) = regex!("^<([a-zA-Z_0-9]{1,16})> (.+)$").captures(&message) {
78 return (Some(m[1].to_string()), m[2].to_string());
79 }
80
81 (None, message)
82 }
83 ChatPacket::Player(p) => (
84 Some(p.chat_type.name.to_string()),
87 p.body.content.clone(),
88 ),
89 ChatPacket::Disguised(p) => (
90 Some(p.chat_type.name.to_string()),
93 p.message.to_string(),
94 ),
95 }
96 }
97
98 pub fn sender(&self) -> Option<String> {
102 self.split_sender_and_content().0
103 }
104
105 pub fn sender_uuid(&self) -> Option<Uuid> {
109 match self {
110 ChatPacket::System(_) => None,
111 ChatPacket::Player(m) => Some(m.sender),
112 ChatPacket::Disguised(_) => None,
113 }
114 }
115
116 pub fn content(&self) -> String {
120 self.split_sender_and_content().1
121 }
122
123 pub fn new(message: &str) -> Self {
126 ChatPacket::System(Arc::new(ClientboundSystemChat {
127 content: FormattedText::from(message),
128 overlay: false,
129 }))
130 }
131
132 pub fn is_whisper(&self) -> bool {
136 match self.message() {
137 FormattedText::Text(_) => false,
138 FormattedText::Translatable(t) => t.key == "commands.message.display.incoming",
139 }
140 }
141}
142
143impl Client {
144 pub fn send_chat_packet(&self, message: &str) {
150 self.ecs.lock().send_event(SendChatKindEvent {
151 entity: self.entity,
152 content: message.to_string(),
153 kind: ChatKind::Message,
154 });
155 let _ = self.run_schedule_sender.try_send(());
156 }
157
158 pub fn send_command_packet(&self, command: &str) {
164 self.ecs.lock().send_event(SendChatKindEvent {
165 entity: self.entity,
166 content: command.to_string(),
167 kind: ChatKind::Command,
168 });
169 let _ = self.run_schedule_sender.try_send(());
170 }
171
172 pub fn chat(&self, content: &str) {
182 self.ecs.lock().send_event(SendChatEvent {
183 entity: self.entity,
184 content: content.to_string(),
185 });
186 let _ = self.run_schedule_sender.try_send(());
187 }
188}
189
190#[derive(Event, Debug, Clone)]
192pub struct ChatReceivedEvent {
193 pub entity: Entity,
194 pub packet: ChatPacket,
195}
196
197#[derive(Event)]
199pub struct SendChatEvent {
200 pub entity: Entity,
201 pub content: String,
202}
203
204pub fn handle_send_chat_event(
205 mut events: EventReader<SendChatEvent>,
206 mut send_chat_kind_events: EventWriter<SendChatKindEvent>,
207) {
208 for event in events.read() {
209 if event.content.starts_with('/') {
210 send_chat_kind_events.send(SendChatKindEvent {
211 entity: event.entity,
212 content: event.content[1..].to_string(),
213 kind: ChatKind::Command,
214 });
215 } else {
216 send_chat_kind_events.send(SendChatKindEvent {
217 entity: event.entity,
218 content: event.content.clone(),
219 kind: ChatKind::Message,
220 });
221 }
222 }
223}
224
225pub enum ChatKind {
227 Message,
228 Command,
229}
230
231