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 super::packet::game::handle_outgoing_packets;
23use crate::client::Client;
24
25pub struct ChatPlugin;
26impl Plugin for ChatPlugin {
27 fn build(&self, app: &mut App) {
28 app.add_event::<SendChatEvent>()
29 .add_event::<SendChatKindEvent>()
30 .add_event::<ChatReceivedEvent>()
31 .add_systems(
32 Update,
33 (
34 handle_send_chat_event,
35 handle_send_chat_kind_event.after(handle_outgoing_packets),
36 )
37 .chain(),
38 );
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub enum ChatPacket {
45 System(Arc<ClientboundSystemChat>),
46 Player(Arc<ClientboundPlayerChat>),
47 Disguised(Arc<ClientboundDisguisedChat>),
48}
49
50macro_rules! regex {
51 ($re:literal $(,)?) => {{
52 static RE: std::sync::LazyLock<regex::Regex> =
53 std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap());
54 &RE
55 }};
56}
57
58impl ChatPacket {
59 pub fn message(&self) -> FormattedText {
61 match self {
62 ChatPacket::System(p) => p.content.clone(),
63 ChatPacket::Player(p) => p.message(),
64 ChatPacket::Disguised(p) => p.message(),
65 }
66 }
67
68 pub fn split_sender_and_content(&self) -> (Option<String>, String) {
73 match self {
74 ChatPacket::System(p) => {
75 let message = p.content.to_string();
76 if p.overlay {
78 return (None, message);
79 }
80 if let Some(m) = regex!("^<([a-zA-Z_0-9]{1,16})> (.+)$").captures(&message) {
83 return (Some(m[1].to_string()), m[2].to_string());
84 }
85
86 (None, message)
87 }
88 ChatPacket::Player(p) => (
89 Some(p.chat_type.name.to_string()),
92 p.body.content.clone(),
93 ),
94 ChatPacket::Disguised(p) => (
95 Some(p.chat_type.name.to_string()),
98 p.message.to_string(),
99 ),
100 }
101 }
102
103 pub fn username(&self) -> Option<String> {
107 self.split_sender_and_content().0
108 }
109
110 pub fn uuid(&self) -> Option<Uuid> {
114 match self {
115 ChatPacket::System(_) => None,
116 ChatPacket::Player(m) => Some(m.sender),
117 ChatPacket::Disguised(_) => None,
118 }
119 }
120
121 pub fn content(&self) -> String {
125 self.split_sender_and_content().1
126 }
127
128 pub fn new(message: &str) -> Self {
131 ChatPacket::System(Arc::new(ClientboundSystemChat {
132 content: FormattedText::from(message),
133 overlay: false,
134 }))
135 }
136
137 pub fn is_whisper(&self) -> bool {
141 match self.message() {
142 FormattedText::Text(_) => false,
143 FormattedText::Translatable(t) => t.key == "commands.message.display.incoming",
144 }
145 }
146}
147
148impl Client {
149 pub fn send_chat_packet(&self, message: &str) {
155 self.ecs.lock().send_event(SendChatKindEvent {
156 entity: self.entity,
157 content: message.to_string(),
158 kind: ChatKind::Message,
159 });
160 let _ = self.run_schedule_sender.try_send(());
161 }
162
163 pub fn send_command_packet(&self, command: &str) {
169 self.ecs.lock().send_event(SendChatKindEvent {
170 entity: self.entity,
171 content: command.to_string(),
172 kind: ChatKind::Command,
173 });
174 let _ = self.run_schedule_sender.try_send(());
175 }
176
177 pub fn chat(&self, content: &str) {
187 self.ecs.lock().send_event(SendChatEvent {
188 entity: self.entity,
189 content: content.to_string(),
190 });
191 let _ = self.run_schedule_sender.try_send(());
192 }
193}
194
195#[derive(Event, Debug, Clone)]
197pub struct ChatReceivedEvent {
198 pub entity: Entity,
199 pub packet: ChatPacket,
200}
201
202#[derive(Event)]
204pub struct SendChatEvent {
205 pub entity: Entity,
206 pub content: String,
207}
208
209pub fn handle_send_chat_event(
210 mut events: EventReader<SendChatEvent>,
211 mut send_chat_kind_events: EventWriter<SendChatKindEvent>,
212) {
213 for event in events.read() {
214 if event.content.starts_with('/') {
215 send_chat_kind_events.send(SendChatKindEvent {
216 entity: event.entity,
217 content: event.content[1..].to_string(),
218 kind: ChatKind::Command,
219 });
220 } else {
221 send_chat_kind_events.send(SendChatKindEvent {
222 entity: event.entity,
223 content: event.content.clone(),
224 kind: ChatKind::Message,
225 });
226 }
227 }
228}
229
230pub enum ChatKind {
232 Message,
233 Command,
234}
235
236