azalea_client/plugins/packet/game/
events.rs

1use std::{
2    io::Cursor,
3    sync::{Arc, Weak},
4};
5
6use azalea_chat::FormattedText;
7use azalea_core::resource_location::ResourceLocation;
8use azalea_protocol::{
9    packets::{
10        Packet,
11        game::{ClientboundGamePacket, ClientboundPlayerCombatKill, ServerboundGamePacket},
12    },
13    read::deserialize_packet,
14};
15use azalea_world::Instance;
16use bevy_ecs::prelude::*;
17use parking_lot::RwLock;
18use tracing::{debug, error};
19use uuid::Uuid;
20
21use crate::{PlayerInfo, client::InGameState, raw_connection::RawConnection};
22
23/// An event that's sent when we receive a packet.
24/// ```
25/// # use azalea_client::packet::game::ReceivePacketEvent;
26/// # use azalea_protocol::packets::game::ClientboundGamePacket;
27/// # use bevy_ecs::event::EventReader;
28///
29/// fn handle_packets(mut events: EventReader<ReceivePacketEvent>) {
30///     for ReceivePacketEvent {
31///         entity,
32///         packet,
33///     } in events.read() {
34///         match packet.as_ref() {
35///             ClientboundGamePacket::LevelParticles(p) => {
36///                 // ...
37///             }
38///             _ => {}
39///         }
40///     }
41/// }
42/// ```
43#[derive(Event, Debug, Clone)]
44pub struct ReceivePacketEvent {
45    /// The client entity that received the packet.
46    pub entity: Entity,
47    /// The packet that was actually received.
48    pub packet: Arc<ClientboundGamePacket>,
49}
50
51/// An event for sending a packet to the server while we're in the `game` state.
52#[derive(Event, Clone, Debug)]
53pub struct SendPacketEvent {
54    pub sent_by: Entity,
55    pub packet: ServerboundGamePacket,
56}
57impl SendPacketEvent {
58    pub fn new(sent_by: Entity, packet: impl Packet<ServerboundGamePacket>) -> Self {
59        let packet = packet.into_variant();
60        Self { sent_by, packet }
61    }
62}
63
64pub fn handle_outgoing_packets_observer(
65    trigger: Trigger<SendPacketEvent>,
66    mut query: Query<(&mut RawConnection, Option<&InGameState>)>,
67) {
68    let event = trigger.event();
69
70    if let Ok((raw_connection, in_game_state)) = query.get_mut(event.sent_by) {
71        if in_game_state.is_none() {
72            error!(
73                "Tried to send a game packet {:?} while not in game state",
74                event.packet
75            );
76            return;
77        }
78
79        // debug!("Sending packet: {:?}", event.packet);
80        if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
81            error!("Failed to send packet: {e}");
82        }
83    }
84}
85
86/// A system that converts [`SendPacketEvent`] events into triggers so they get
87/// received by [`handle_outgoing_packets_observer`].
88pub fn handle_outgoing_packets(mut commands: Commands, mut events: EventReader<SendPacketEvent>) {
89    for event in events.read() {
90        commands.trigger(event.clone());
91    }
92}
93
94pub fn emit_receive_packet_events(
95    query: Query<(Entity, &RawConnection), With<InGameState>>,
96    mut packet_events: ResMut<Events<ReceivePacketEvent>>,
97) {
98    // we manually clear and send the events at the beginning of each update
99    // since otherwise it'd cause issues with events in process_packet_events
100    // running twice
101    packet_events.clear();
102    for (player_entity, raw_connection) in &query {
103        let packets_lock = raw_connection.incoming_packet_queue();
104        let mut packets = packets_lock.lock();
105        if !packets.is_empty() {
106            let mut packets_read = 0;
107            for raw_packet in packets.iter() {
108                packets_read += 1;
109                let packet =
110                    match deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new(raw_packet))
111                    {
112                        Ok(packet) => packet,
113                        Err(err) => {
114                            error!("failed to read packet: {err:?}");
115                            debug!("packet bytes: {raw_packet:?}");
116                            continue;
117                        }
118                    };
119
120                let should_interrupt = packet_interrupts(&packet);
121
122                packet_events.send(ReceivePacketEvent {
123                    entity: player_entity,
124                    packet: Arc::new(packet),
125                });
126
127                if should_interrupt {
128                    break;
129                }
130            }
131            packets.drain(0..packets_read);
132        }
133    }
134}
135
136/// Whether the given packet should make us stop deserializing the received
137/// packets until next update.
138///
139/// This is used for packets that can switch the client state.
140fn packet_interrupts(packet: &ClientboundGamePacket) -> bool {
141    matches!(
142        packet,
143        ClientboundGamePacket::StartConfiguration(_)
144            | ClientboundGamePacket::Disconnect(_)
145            | ClientboundGamePacket::Transfer(_)
146    )
147}
148
149/// A player joined the game (or more specifically, was added to the tab
150/// list of a local player).
151#[derive(Event, Debug, Clone)]
152pub struct AddPlayerEvent {
153    /// The local player entity that received this event.
154    pub entity: Entity,
155    pub info: PlayerInfo,
156}
157/// A player left the game (or maybe is still in the game and was just
158/// removed from the tab list of a local player).
159#[derive(Event, Debug, Clone)]
160pub struct RemovePlayerEvent {
161    /// The local player entity that received this event.
162    pub entity: Entity,
163    pub info: PlayerInfo,
164}
165/// A player was updated in the tab list of a local player (gamemode, display
166/// name, or latency changed).
167#[derive(Event, Debug, Clone)]
168pub struct UpdatePlayerEvent {
169    /// The local player entity that received this event.
170    pub entity: Entity,
171    pub info: PlayerInfo,
172}
173
174/// Event for when an entity dies. dies. If it's a local player and there's a
175/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
176/// be included.
177#[derive(Event, Debug, Clone)]
178pub struct DeathEvent {
179    pub entity: Entity,
180    pub packet: Option<ClientboundPlayerCombatKill>,
181}
182
183/// A KeepAlive packet is sent from the server to verify that the client is
184/// still connected.
185#[derive(Event, Debug, Clone)]
186pub struct KeepAliveEvent {
187    pub entity: Entity,
188    /// The ID of the keepalive. This is an arbitrary number, but vanilla
189    /// servers use the time to generate this.
190    pub id: u64,
191}
192
193#[derive(Event, Debug, Clone)]
194pub struct ResourcePackEvent {
195    pub entity: Entity,
196    /// The random ID for this request to download the resource pack. The packet
197    /// for replying to a resource pack push must contain the same ID.
198    pub id: Uuid,
199    pub url: String,
200    pub hash: String,
201    pub required: bool,
202    pub prompt: Option<FormattedText>,
203}
204
205/// An instance (aka world, dimension) was loaded by a client.
206///
207/// Since the instance is given to you as a weak reference, it won't be able to
208/// be `upgrade`d if all local players leave it.
209#[derive(Event, Debug, Clone)]
210pub struct InstanceLoadedEvent {
211    pub entity: Entity,
212    pub name: ResourceLocation,
213    pub instance: Weak<RwLock<Instance>>,
214}
215
216/// A Bevy trigger that's sent when our client receives a [`ClientboundPing`]
217/// packet in the game state.
218///
219/// Also see [`ConfigPingEvent`] which is used for the config state.
220///
221/// This is not an event and can't be listened to from a normal system,
222///so  `EventReader<PingEvent>` will not work.
223///
224/// To use it, add your "system" with `add_observer` instead of `add_systems`
225/// and use `Trigger<PingEvent>` instead of `EventReader`.
226///
227/// The client Entity that received the packet will be attached to the trigger.
228///
229/// [`ClientboundPing`]: azalea_protocol::packets::game::ClientboundPing
230/// [`ConfigPingEvent`]: crate::packet::config::ConfigPingEvent
231#[derive(Event, Debug, Clone)]
232pub struct PingEvent(pub azalea_protocol::packets::game::ClientboundPing);