azalea_client/plugins/
events.rs

1//! Defines the [`Event`] enum and makes those events trigger when they're sent
2//! in the ECS.
3
4use std::sync::Arc;
5
6use azalea_chat::FormattedText;
7use azalea_core::tick::GameTick;
8use azalea_entity::{Dead, InLoadedChunk};
9use azalea_protocol::packets::game::{
10    ClientboundGamePacket, c_player_combat_kill::ClientboundPlayerCombatKill,
11};
12use azalea_world::{InstanceName, MinecraftEntityId};
13use bevy_app::{App, Plugin, PreUpdate, Update};
14use bevy_ecs::{
15    component::Component,
16    entity::Entity,
17    event::EventReader,
18    query::{Added, With, Without},
19    schedule::IntoSystemConfigs,
20    system::{Commands, Query},
21};
22use derive_more::{Deref, DerefMut};
23use tokio::sync::mpsc;
24
25use crate::{
26    PlayerInfo,
27    chat::{ChatPacket, ChatReceivedEvent},
28    disconnect::DisconnectEvent,
29    packet::game::{
30        AddPlayerEvent, DeathEvent, KeepAliveEvent, ReceivePacketEvent, RemovePlayerEvent,
31        UpdatePlayerEvent,
32    },
33};
34
35// (for contributors):
36// HOW TO ADD A NEW (packet based) EVENT:
37// - Add it as an ECS event first:
38//     - Make a struct that contains an entity field and some data fields (look
39//       in packet/game/events.rs for examples. These structs should always have
40//       their names end with "Event".
41//         - (the `entity` field is the local player entity that's receiving the
42//           event)
43//     - In the GamePacketHandler, you always have a `player` field that you can
44//       use.
45//     - Add the event struct in PacketPlugin::build
46//         - (in the `impl Plugin for PacketPlugin`)
47//     - To get the event writer, you have to get an EventWriter<ThingEvent>.
48//       Look at other packets in packet/game/mod.rs for examples.
49//
50// At this point, you've created a new ECS event. That's annoying for bots to
51// use though, so you might wanna add it to the Event enum too:
52//     - In this file, add a new variant to that Event enum with the same name
53//       as your event (without the "Event" suffix).
54//     - Create a new system function like the other ones here, and put that
55//       system function in the `impl Plugin for EventsPlugin`
56
57/// Something that happened in-game, such as a tick passing or chat message
58/// being sent.
59///
60/// Note: Events are sent before they're processed, so for example game ticks
61/// happen at the beginning of a tick before anything has happened.
62#[derive(Debug, Clone)]
63#[non_exhaustive]
64pub enum Event {
65    /// Happens right after the bot switches into the Game state, but before
66    /// it's actually spawned. This can be useful for setting the client
67    /// information with `Client::set_client_information`, so the packet
68    /// doesn't have to be sent twice.
69    ///
70    /// You may want to use [`Event::Spawn`] instead to wait for the bot to be
71    /// in the world.
72    Init,
73    /// Fired when we receive a login packet, which is after [`Event::Init`] but
74    /// before [`Event::Spawn`]. You usually want [`Event::Spawn`] instead.
75    ///
76    /// Your position may be [`Vec3::ZERO`] immediately after you receive this
77    /// event, but it'll be ready by the time you get [`Event::Spawn`].
78    ///
79    /// [`Vec3::ZERO`]: azalea_core::position::Vec3::ZERO
80    Login,
81    /// Fired when the player fully spawns into the world and is ready to
82    /// interact with it.
83    ///
84    /// This is usually the event you should listen for when waiting for the bot
85    /// to be ready.
86    Spawn,
87    /// A chat message was sent in the game chat.
88    Chat(ChatPacket),
89    /// Happens 20 times per second, but only when the world is loaded.
90    Tick,
91    /// We received a packet from the server.
92    ///
93    /// ```
94    /// # use azalea_client::Event;
95    /// # use azalea_protocol::packets::game::ClientboundGamePacket;
96    /// # async fn example(event: Event) {
97    /// # match event {
98    /// Event::Packet(packet) => match *packet {
99    ///     ClientboundGamePacket::Login(_) => {
100    ///         println!("login packet");
101    ///     }
102    ///     _ => {}
103    /// },
104    /// # _ => {}
105    /// # }
106    /// # }
107    /// ```
108    Packet(Arc<ClientboundGamePacket>),
109    /// A player joined the game (or more specifically, was added to the tab
110    /// list).
111    AddPlayer(PlayerInfo),
112    /// A player left the game (or maybe is still in the game and was just
113    /// removed from the tab list).
114    RemovePlayer(PlayerInfo),
115    /// A player was updated in the tab list (gamemode, display
116    /// name, or latency changed).
117    UpdatePlayer(PlayerInfo),
118    /// The client player died in-game.
119    Death(Option<Arc<ClientboundPlayerCombatKill>>),
120    /// A `KeepAlive` packet was sent by the server.
121    KeepAlive(u64),
122    /// The client disconnected from the server.
123    Disconnect(Option<FormattedText>),
124}
125
126/// A component that contains an event sender for events that are only
127/// received by local players. The receiver for this is returned by
128/// [`Client::start_client`].
129///
130/// [`Client::start_client`]: crate::Client::start_client
131#[derive(Component, Deref, DerefMut)]
132pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>);
133
134pub struct EventsPlugin;
135impl Plugin for EventsPlugin {
136    fn build(&self, app: &mut App) {
137        app.add_systems(
138            Update,
139            (
140                chat_listener,
141                login_listener,
142                spawn_listener,
143                packet_listener,
144                add_player_listener,
145                update_player_listener,
146                remove_player_listener,
147                keepalive_listener,
148                death_listener,
149                disconnect_listener,
150            ),
151        )
152        .add_systems(
153            PreUpdate,
154            init_listener.before(crate::packet::game::process_packet_events),
155        )
156        .add_systems(GameTick, tick_listener);
157    }
158}
159
160// when LocalPlayerEvents is added, it means the client just started
161pub fn init_listener(query: Query<&LocalPlayerEvents, Added<LocalPlayerEvents>>) {
162    for local_player_events in &query {
163        let _ = local_player_events.send(Event::Init);
164    }
165}
166
167// when MinecraftEntityId is added, it means the player is now in the world
168pub fn login_listener(query: Query<&LocalPlayerEvents, Added<MinecraftEntityId>>) {
169    for local_player_events in &query {
170        let _ = local_player_events.send(Event::Login);
171    }
172}
173
174/// A unit struct component that indicates that the entity has sent
175/// [`Event::Spawn`].
176///
177/// This is just used internally by the [`spawn_listener`] system to avoid
178/// sending the event twice for the same client.
179#[derive(Component)]
180pub struct SentSpawnEvent;
181#[allow(clippy::type_complexity)]
182pub fn spawn_listener(
183    query: Query<(Entity, &LocalPlayerEvents), (Added<InLoadedChunk>, Without<SentSpawnEvent>)>,
184    mut commands: Commands,
185) {
186    for (entity, local_player_events) in &query {
187        let _ = local_player_events.send(Event::Spawn);
188        commands.entity(entity).insert(SentSpawnEvent);
189    }
190}
191
192pub fn chat_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<ChatReceivedEvent>) {
193    for event in events.read() {
194        if let Ok(local_player_events) = query.get(event.entity) {
195            let _ = local_player_events.send(Event::Chat(event.packet.clone()));
196        }
197    }
198}
199
200// only tick if we're in a world
201pub fn tick_listener(query: Query<&LocalPlayerEvents, With<InstanceName>>) {
202    for local_player_events in &query {
203        let _ = local_player_events.send(Event::Tick);
204    }
205}
206
207pub fn packet_listener(
208    query: Query<&LocalPlayerEvents>,
209    mut events: EventReader<ReceivePacketEvent>,
210) {
211    for event in events.read() {
212        if let Ok(local_player_events) = query.get(event.entity) {
213            let _ = local_player_events.send(Event::Packet(event.packet.clone()));
214        }
215    }
216}
217
218pub fn add_player_listener(
219    query: Query<&LocalPlayerEvents>,
220    mut events: EventReader<AddPlayerEvent>,
221) {
222    for event in events.read() {
223        let local_player_events = query
224            .get(event.entity)
225            .expect("Non-local entities shouldn't be able to receive add player events");
226        let _ = local_player_events.send(Event::AddPlayer(event.info.clone()));
227    }
228}
229
230pub fn update_player_listener(
231    query: Query<&LocalPlayerEvents>,
232    mut events: EventReader<UpdatePlayerEvent>,
233) {
234    for event in events.read() {
235        let local_player_events = query
236            .get(event.entity)
237            .expect("Non-local entities shouldn't be able to receive update player events");
238        let _ = local_player_events.send(Event::UpdatePlayer(event.info.clone()));
239    }
240}
241
242pub fn remove_player_listener(
243    query: Query<&LocalPlayerEvents>,
244    mut events: EventReader<RemovePlayerEvent>,
245) {
246    for event in events.read() {
247        let local_player_events = query
248            .get(event.entity)
249            .expect("Non-local entities shouldn't be able to receive remove player events");
250        let _ = local_player_events.send(Event::RemovePlayer(event.info.clone()));
251    }
252}
253
254pub fn death_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<DeathEvent>) {
255    for event in events.read() {
256        if let Ok(local_player_events) = query.get(event.entity) {
257            let _ = local_player_events.send(Event::Death(event.packet.clone().map(|p| p.into())));
258        }
259    }
260}
261
262/// Send the "Death" event for [`LocalEntity`]s that died with no reason.
263///
264/// [`LocalEntity`]: azalea_entity::LocalEntity
265pub fn dead_component_listener(query: Query<&LocalPlayerEvents, Added<Dead>>) {
266    for local_player_events in &query {
267        local_player_events.send(Event::Death(None)).unwrap();
268    }
269}
270
271pub fn keepalive_listener(
272    query: Query<&LocalPlayerEvents>,
273    mut events: EventReader<KeepAliveEvent>,
274) {
275    for event in events.read() {
276        let local_player_events = query
277            .get(event.entity)
278            .expect("Non-local entities shouldn't be able to receive keepalive events");
279        let _ = local_player_events.send(Event::KeepAlive(event.id));
280    }
281}
282
283pub fn disconnect_listener(
284    query: Query<&LocalPlayerEvents>,
285    mut events: EventReader<DisconnectEvent>,
286) {
287    for event in events.read() {
288        if let Ok(local_player_events) = query.get(event.entity) {
289            let _ = local_player_events.send(Event::Disconnect(event.reason.clone()));
290        }
291    }
292}