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    Init,
62    /// The client is now in the world. Fired when we receive a login packet.
63    Login,
64    /// A chat message was sent in the game chat.
65    Chat(ChatPacket),
66    /// Happens 20 times per second, but only when the world is loaded.
67    Tick,
68    /// We received a packet from the server.
69    ///
70    /// ```
71    /// # use azalea_client::Event;
72    /// # use azalea_protocol::packets::game::ClientboundGamePacket;
73    /// # async fn example(event: Event) {
74    /// # match event {
75    /// Event::Packet(packet) => match *packet {
76    ///     ClientboundGamePacket::Login(_) => {
77    ///         println!("login packet");
78    ///     }
79    ///     _ => {}
80    /// },
81    /// # _ => {}
82    /// # }
83    /// # }
84    /// ```
85    Packet(Arc<ClientboundGamePacket>),
86    /// A player joined the game (or more specifically, was added to the tab
87    /// list).
88    AddPlayer(PlayerInfo),
89    /// A player left the game (or maybe is still in the game and was just
90    /// removed from the tab list).
91    RemovePlayer(PlayerInfo),
92    /// A player was updated in the tab list (gamemode, display
93    /// name, or latency changed).
94    UpdatePlayer(PlayerInfo),
95    /// The client player died in-game.
96    Death(Option<Arc<ClientboundPlayerCombatKill>>),
97    /// A `KeepAlive` packet was sent by the server.
98    KeepAlive(u64),
99    /// The client disconnected from the server.
100    Disconnect(Option<FormattedText>),
101}
102
103/// A component that contains an event sender for events that are only
104/// received by local players. The receiver for this is returned by
105/// [`Client::start_client`].
106///
107/// [`Client::start_client`]: crate::Client::start_client
108#[derive(Component, Deref, DerefMut)]
109pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>);
110
111pub struct EventPlugin;
112impl Plugin for EventPlugin {
113    fn build(&self, app: &mut App) {
114        app.add_systems(
115            Update,
116            (
117                chat_listener,
118                login_listener,
119                packet_listener,
120                add_player_listener,
121                update_player_listener,
122                remove_player_listener,
123                keepalive_listener,
124                death_listener,
125                disconnect_listener,
126            ),
127        )
128        .add_systems(
129            PreUpdate,
130            init_listener.before(crate::packet_handling::game::process_packet_events),
131        )
132        .add_systems(GameTick, tick_listener);
133    }
134}
135
136// when LocalPlayerEvents is added, it means the client just started
137pub fn init_listener(query: Query<&LocalPlayerEvents, Added<LocalPlayerEvents>>) {
138    for local_player_events in &query {
139        let _ = local_player_events.send(Event::Init);
140    }
141}
142
143// when MinecraftEntityId is added, it means the player is now in the world
144pub fn login_listener(query: Query<&LocalPlayerEvents, Added<MinecraftEntityId>>) {
145    for local_player_events in &query {
146        let _ = local_player_events.send(Event::Login);
147    }
148}
149
150pub fn chat_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<ChatReceivedEvent>) {
151    for event in events.read() {
152        let local_player_events = query
153            .get(event.entity)
154            .expect("Non-local entities shouldn't be able to receive chat events");
155        let _ = local_player_events.send(Event::Chat(event.packet.clone()));
156    }
157}
158
159// only tick if we're in a world
160pub fn tick_listener(query: Query<&LocalPlayerEvents, With<InstanceName>>) {
161    for local_player_events in &query {
162        let _ = local_player_events.send(Event::Tick);
163    }
164}
165
166pub fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<PacketEvent>) {
167    for event in events.read() {
168        let local_player_events = query
169            .get(event.entity)
170            .expect("Non-local entities shouldn't be able to receive packet events");
171        let _ = local_player_events.send(Event::Packet(event.packet.clone()));
172    }
173}
174
175pub fn add_player_listener(
176    query: Query<&LocalPlayerEvents>,
177    mut events: EventReader<AddPlayerEvent>,
178) {
179    for event in events.read() {
180        let local_player_events = query
181            .get(event.entity)
182            .expect("Non-local entities shouldn't be able to receive add player events");
183        let _ = local_player_events.send(Event::AddPlayer(event.info.clone()));
184    }
185}
186
187pub fn update_player_listener(
188    query: Query<&LocalPlayerEvents>,
189    mut events: EventReader<UpdatePlayerEvent>,
190) {
191    for event in events.read() {
192        let local_player_events = query
193            .get(event.entity)
194            .expect("Non-local entities shouldn't be able to receive update player events");
195        let _ = local_player_events.send(Event::UpdatePlayer(event.info.clone()));
196    }
197}
198
199pub fn remove_player_listener(
200    query: Query<&LocalPlayerEvents>,
201    mut events: EventReader<RemovePlayerEvent>,
202) {
203    for event in events.read() {
204        let local_player_events = query
205            .get(event.entity)
206            .expect("Non-local entities shouldn't be able to receive remove player events");
207        let _ = local_player_events.send(Event::RemovePlayer(event.info.clone()));
208    }
209}
210
211pub fn death_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<DeathEvent>) {
212    for event in events.read() {
213        if let Ok(local_player_events) = query.get(event.entity) {
214            let _ = local_player_events.send(Event::Death(event.packet.clone().map(|p| p.into())));
215        }
216    }
217}
218
219pub fn keepalive_listener(
220    query: Query<&LocalPlayerEvents>,
221    mut events: EventReader<KeepAliveEvent>,
222) {
223    for event in events.read() {
224        let local_player_events = query
225            .get(event.entity)
226            .expect("Non-local entities shouldn't be able to receive keepalive events");
227        let _ = local_player_events.send(Event::KeepAlive(event.id));
228    }
229}
230
231pub fn disconnect_listener(
232    query: Query<&LocalPlayerEvents>,
233    mut events: EventReader<DisconnectEvent>,
234) {
235    for event in events.read() {
236        if let Ok(local_player_events) = query.get(event.entity) {
237            let _ = local_player_events.send(Event::Disconnect(event.reason.clone()));
238        }
239    }
240}