azalea_client/plugins/packet/config/
events.rs

1use std::io::Cursor;
2
3use azalea_protocol::{
4    packets::{
5        Packet,
6        config::{ClientboundConfigPacket, ServerboundConfigPacket},
7    },
8    read::deserialize_packet,
9};
10use bevy_ecs::prelude::*;
11use tracing::{debug, error};
12
13use crate::{InConfigState, raw_connection::RawConnection};
14
15#[derive(Event, Debug, Clone)]
16pub struct ReceiveConfigPacketEvent {
17    /// The client entity that received the packet.
18    pub entity: Entity,
19    /// The packet that was actually received.
20    pub packet: ClientboundConfigPacket,
21}
22
23/// An event for sending a packet to the server while we're in the
24/// `configuration` state.
25#[derive(Event, Clone)]
26pub struct SendConfigPacketEvent {
27    pub sent_by: Entity,
28    pub packet: ServerboundConfigPacket,
29}
30impl SendConfigPacketEvent {
31    pub fn new(sent_by: Entity, packet: impl Packet<ServerboundConfigPacket>) -> Self {
32        let packet = packet.into_variant();
33        Self { sent_by, packet }
34    }
35}
36
37pub fn handle_outgoing_packets_observer(
38    trigger: Trigger<SendConfigPacketEvent>,
39    mut query: Query<(&mut RawConnection, Option<&InConfigState>)>,
40) {
41    let event = trigger.event();
42    if let Ok((raw_conn, in_configuration_state)) = query.get_mut(event.sent_by) {
43        if in_configuration_state.is_none() {
44            error!(
45                "Tried to send a configuration packet {:?} while not in configuration state",
46                event.packet
47            );
48            return;
49        }
50        debug!("Sending packet: {:?}", event.packet);
51        if let Err(e) = raw_conn.write_packet(event.packet.clone()) {
52            error!("Failed to send packet: {e}");
53        }
54    }
55}
56/// A system that converts [`SendConfigPacketEvent`] events into triggers so
57/// they get received by [`handle_outgoing_packets_observer`].
58pub fn handle_outgoing_packets(
59    mut commands: Commands,
60    mut events: EventReader<SendConfigPacketEvent>,
61) {
62    for event in events.read() {
63        commands.trigger(event.clone());
64    }
65}
66
67pub fn emit_receive_config_packet_events(
68    query: Query<(Entity, &RawConnection), With<InConfigState>>,
69    mut packet_events: ResMut<Events<ReceiveConfigPacketEvent>>,
70) {
71    // we manually clear and send the events at the beginning of each update
72    // since otherwise it'd cause issues with events in process_packet_events
73    // running twice
74    packet_events.clear();
75    for (player_entity, raw_conn) in &query {
76        let packets_lock = raw_conn.incoming_packet_queue();
77        let mut packets = packets_lock.lock();
78        if !packets.is_empty() {
79            let mut packets_read = 0;
80            for raw_packet in packets.iter() {
81                packets_read += 1;
82                let packet = match deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(
83                    raw_packet,
84                )) {
85                    Ok(packet) => packet,
86                    Err(err) => {
87                        error!("failed to read packet: {err:?}");
88                        debug!("packet bytes: {raw_packet:?}");
89                        continue;
90                    }
91                };
92
93                let should_interrupt = packet_interrupts(&packet);
94
95                packet_events.send(ReceiveConfigPacketEvent {
96                    entity: player_entity,
97                    packet,
98                });
99
100                if should_interrupt {
101                    break;
102                }
103            }
104            packets.drain(0..packets_read);
105        }
106    }
107}
108
109/// Whether the given packet should make us stop deserializing the received
110/// packets until next update.
111///
112/// This is used for packets that can switch the client state.
113fn packet_interrupts(packet: &ClientboundConfigPacket) -> bool {
114    matches!(
115        packet,
116        ClientboundConfigPacket::FinishConfiguration(_)
117            | ClientboundConfigPacket::Disconnect(_)
118            | ClientboundConfigPacket::Transfer(_)
119    )
120}
121
122/// A Bevy trigger that's sent when our client receives a [`ClientboundPing`]
123/// packet in the config state.
124///
125/// See [`PingEvent`] for more information.
126///
127/// [`ClientboundPing`]: azalea_protocol::packets::config::ClientboundPing
128/// [`PingEvent`]: crate::packet::game::PingEvent
129#[derive(Event, Debug, Clone)]
130pub struct ConfigPingEvent(pub azalea_protocol::packets::config::ClientboundPing);