azalea_client/
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_protocol::packets::game::{
9    c_player_combat_kill::ClientboundPlayerCombatKill, ClientboundGamePacket,
10};
11use azalea_world::{InstanceName, MinecraftEntityId};
12use bevy_app::{App, Plugin, PreUpdate, Update};
13use bevy_ecs::{
14    component::Component,
15    event::EventReader,
16    query::{Added, With},
17    schedule::IntoSystemConfigs,
18    system::Query,
19};
20use derive_more::{Deref, DerefMut};
21use tokio::sync::mpsc;
22
23use crate::{
24    chat::{ChatPacket, ChatReceivedEvent},
25    disconnect::DisconnectEvent,
26    packet_handling::game::{
27        AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketEvent, RemovePlayerEvent,
28        UpdatePlayerEvent,
29    },
30    PlayerInfo,
31};
32
33// (for contributors):
34// HOW TO ADD A NEW (packet based) EVENT:
35// - make a struct that contains an entity field and a data field (look in
36//   packet_handling.rs for examples, also you should end the struct name with
37//   "Event")
38// - the entity field is the local player entity that's receiving the event
39// - in packet_handling, you always have a variable called player_entity that
40//   you can use
41// - add the event struct in the `impl Plugin for PacketHandlerPlugin`
42// - to get the event writer, you have to get an
43//   EventWriter<SomethingHappenedEvent> from the SystemState (the convention is
44//   to end your variable with the word "events", like "something_events")
45//
46// - then here in this file, add it to the Event enum
47// - and make an event listener system/function like the other ones and put the
48//   function in the `impl Plugin for EventPlugin`
49
50/// Something that happened in-game, such as a tick passing or chat message
51/// being sent.
52///
53/// Note: Events are sent before they're processed, so for example game ticks
54/// happen at the beginning of a tick before anything has happened.
55#[derive(Debug, Clone)]
56pub enum Event {
57    /// Happens right after the bot switches into the Game state, but before
58    /// it's actually spawned. This can be useful for setting the client
59    /// information with `Client::set_client_information`, so the packet
60    /// doesn't have to be sent twice.
61    ///
62    /// You may want to use [`Event::Login`] instead to wait for the bot to be
63    /// in the world.
64    Init,
65    /// The client is now in the world. Fired when we receive a login packet.
66    Login,
67    /// A chat message was sent in the game chat.
68    Chat(ChatPacket),
69    /// Happens 20 times per second, but only when the world is loaded.
70    Tick,
71    /// We received a packet from the server.
72    ///
73    /// ```
74    /// # use azalea_client::Event;
75    /// # use azalea_protocol::packets::game::ClientboundGamePacket;
76    /// # async fn example(event: Event) {
77    /// # match event {
78    /// Event::Packet(packet) => match *packet {
79    ///     ClientboundGamePacket::Login(_) => {
80    ///         println!("login packet");
81    ///     }
82    ///     _ => {}
83    /// },
84    /// # _ => {}
85    /// # }
86    /// # }
87    /// ```
88    Packet(Arc<ClientboundGamePacket>),
89    /// A player joined the game (or more specifically, was added to the tab
90    /// list).
91    AddPlayer(PlayerInfo),
92    /// A player left the game (or maybe is still in the game and was just
93    /// removed from the tab list).
94    RemovePlayer(PlayerInfo),
95    /// A player was updated in the tab list (gamemode, display
96    /// name, or latency changed).
97    UpdatePlayer(PlayerInfo),
98    /// The client player died in-game.
99    Death(Option<Arc<ClientboundPlayerCombatKill>>),
100    /// A `KeepAlive` packet was sent by the server.
101    KeepAlive(u64),
102    /// The client disconnected from the server.
103    Disconnect(Option<FormattedText>),
104}
105
106/// A component that contains an event sender for events that are only
107/// received by local players. The receiver for this is returned by
108/// [`Client::start_client`].
109///
110/// [`Client::start_client`]: crate::Client::start_client
111#[derive(Component, Deref, DerefMut)]
112pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>);
113
114pub struct EventPlugin;
115impl Plugin for EventPlugin {
116    fn build(&self, app: &mut App) {
117        app.add_systems(
118            Update,
119            (
120                chat_listener,
121                login_listener,
122                packet_listener,
123                add_player_listener,
124                update_player_listener,
125                remove_player_listener,
126                keepalive_listener,
127                death_listener,
128                disconnect_listener,
129            ),
130        )
131        .add_systems(
132            PreUpdate,
133            init_listener.before(crate::packet_handling::game::process_packet_events),
134        )
135        .add_systems(GameTick, tick_listener);
136    }
137}
138
139// when LocalPlayerEvents is added, it means the client just started
140pub fn init_listener(query: Query<&LocalPlayerEvents, Added<LocalPlayerEvents>>) {
141    for local_player_events in &query {
142        let _ = local_player_events.send(Event::Init);
143    }
144}
145
146// when MinecraftEntityId is added, it means the player is now in the world
147pub fn login_listener(query: Query<&LocalPlayerEvents, Added<MinecraftEntityId>>) {
148    for local_player_events in &query {
149        let _ = local_player_events.send(Event::Login);
150    }
151}
152
153pub fn chat_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<ChatReceivedEvent>) {
154    for event in events.read() {
155        let local_player_events = query
156            .get(event.entity)
157            .expect("Non-local entities shouldn't be able to receive chat events");
158        let _ = local_player_events.send(Event::Chat(event.packet.clone()));
159    }
160}
161
162// only tick if we're in a world
163pub fn tick_listener(query: Query<&LocalPlayerEvents, With<InstanceName>>) {
164    for local_player_events in &query {
165        let _ = local_player_events.send(Event::Tick);
166    }
167}
168
169pub fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<PacketEvent>) {
170    for event in events.read() {
171        let local_player_events = query
172            .get(event.entity)
173            .expect("Non-local entities shouldn't be able to receive packet events");
174        let _ = local_player_events.send(Event::Packet(event.packet.clone()));
175    }
176}
177
178pub fn add_player_listener(
179    query: Query<&LocalPlayerEvents>,
180    mut events: EventReader<AddPlayerEvent>,
181) {
182    for event in events.read() {
183        let local_player_events = query
184            .get(event.entity)
185            .expect("Non-local entities shouldn't be able to receive add player events");
186        let _ = local_player_events.send(Event::AddPlayer(event.info.clone()));
187    }
188}
189
190pub fn update_player_listener(
191    query: Query<&LocalPlayerEvents>,
192    mut events: EventReader<UpdatePlayerEvent>,
193) {
194    for event in events.read() {
195        let local_player_events = query
196            .get(event.entity)
197            .expect("Non-local entities shouldn't be able to receive update player events");
198        let _ = local_player_events.send(Event::UpdatePlayer(event.info.clone()));
199    }
200}
201
202pub fn remove_player_listener(
203    query: Query<&LocalPlayerEvents>,
204    mut events: EventReader<RemovePlayerEvent>,
205) {
206    for event in events.read() {
207        let local_player_events = query
208            .get(event.entity)
209            .expect("Non-local entities shouldn't be able to receive remove player events");
210        let _ = local_player_events.send(Event::RemovePlayer(event.info.clone()));
211    }
212}
213
214pub fn death_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<DeathEvent>) {
215    for event in events.read() {
216        if let Ok(local_player_events) = query.get(event.entity) {
217            let _ = local_player_events.send(Event::Death(event.packet.clone().map(|p| p.into())));
218        }
219    }
220}
221
222pub fn keepalive_listener(
223    query: Query<&LocalPlayerEvents>,
224    mut events: EventReader<KeepAliveEvent>,
225) {
226    for event in events.read() {
227        let local_player_events = query
228            .get(event.entity)
229            .expect("Non-local entities shouldn't be able to receive keepalive events");
230        let _ = local_player_events.send(Event::KeepAlive(event.id));
231    }
232}
233
234pub fn disconnect_listener(
235    query: Query<&LocalPlayerEvents>,
236    mut events: EventReader<DisconnectEvent>,
237) {
238    for event in events.read() {
239        if let Ok(local_player_events) = query.get(event.entity) {
240            let _ = local_player_events.send(Event::Disconnect(event.reason.clone()));
241        }
242    }
243}