azalea_client/plugins/packet/game/
events.rs

1use std::sync::{Arc, Weak};
2
3use azalea_chat::FormattedText;
4use azalea_core::resource_location::ResourceLocation;
5use azalea_protocol::packets::{
6    Packet,
7    game::{ClientboundGamePacket, ClientboundPlayerCombatKill, ServerboundGamePacket},
8};
9use azalea_world::Instance;
10use bevy_ecs::prelude::*;
11use parking_lot::RwLock;
12use tracing::{error, trace};
13use uuid::Uuid;
14
15use crate::{PlayerInfo, client::InGameState, connection::RawConnection};
16
17/// An event that's sent when we receive a packet.
18/// ```
19/// # use azalea_client::packet::game::ReceiveGamePacketEvent;
20/// # use azalea_protocol::packets::game::ClientboundGamePacket;
21/// # use bevy_ecs::event::EventReader;
22///
23/// fn handle_packets(mut events: EventReader<ReceiveGamePacketEvent>) {
24///     for ReceiveGamePacketEvent {
25///         entity,
26///         packet,
27///     } in events.read() {
28///         match packet.as_ref() {
29///             ClientboundGamePacket::LevelParticles(p) => {
30///                 // ...
31///             }
32///             _ => {}
33///         }
34///     }
35/// }
36/// ```
37#[derive(Event, Debug, Clone)]
38pub struct ReceiveGamePacketEvent {
39    /// The client entity that received the packet.
40    pub entity: Entity,
41    /// The packet that was actually received.
42    pub packet: Arc<ClientboundGamePacket>,
43}
44
45/// An event for sending a packet to the server while we're in the `game` state.
46#[derive(Event, Clone, Debug)]
47pub struct SendPacketEvent {
48    pub sent_by: Entity,
49    pub packet: ServerboundGamePacket,
50}
51impl SendPacketEvent {
52    pub fn new(sent_by: Entity, packet: impl Packet<ServerboundGamePacket>) -> Self {
53        let packet = packet.into_variant();
54        Self { sent_by, packet }
55    }
56}
57
58pub fn handle_outgoing_packets_observer(
59    trigger: Trigger<SendPacketEvent>,
60    mut query: Query<(&mut RawConnection, Option<&InGameState>)>,
61) {
62    let event = trigger.event();
63    trace!("Sending game packet: {:?}", event.packet);
64
65    if let Ok((mut raw_connection, in_game_state)) = query.get_mut(event.sent_by) {
66        if in_game_state.is_none() {
67            error!(
68                "Tried to send a game packet {:?} while not in game state",
69                event.packet
70            );
71            return;
72        }
73
74        // debug!("Sending game packet: {:?}", event.packet);
75        if let Err(e) = raw_connection.write(event.packet.clone()) {
76            error!("Failed to send packet: {e}");
77        }
78    }
79}
80
81/// A system that converts [`SendPacketEvent`] events into triggers so they get
82/// received by [`handle_outgoing_packets_observer`].
83pub fn handle_outgoing_packets(mut commands: Commands, mut events: EventReader<SendPacketEvent>) {
84    for event in events.read() {
85        commands.trigger(event.clone());
86    }
87}
88
89/// A player joined the game (or more specifically, was added to the tab
90/// list of a local player).
91#[derive(Event, Debug, Clone)]
92pub struct AddPlayerEvent {
93    /// The local player entity that received this event.
94    pub entity: Entity,
95    pub info: PlayerInfo,
96}
97/// A player left the game (or maybe is still in the game and was just
98/// removed from the tab list of a local player).
99#[derive(Event, Debug, Clone)]
100pub struct RemovePlayerEvent {
101    /// The local player entity that received this event.
102    pub entity: Entity,
103    pub info: PlayerInfo,
104}
105/// A player was updated in the tab list of a local player (gamemode, display
106/// name, or latency changed).
107#[derive(Event, Debug, Clone)]
108pub struct UpdatePlayerEvent {
109    /// The local player entity that received this event.
110    pub entity: Entity,
111    pub info: PlayerInfo,
112}
113
114/// Event for when an entity dies. dies. If it's a local player and there's a
115/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
116/// be included.
117#[derive(Event, Debug, Clone)]
118pub struct DeathEvent {
119    pub entity: Entity,
120    pub packet: Option<ClientboundPlayerCombatKill>,
121}
122
123/// A KeepAlive packet is sent from the server to verify that the client is
124/// still connected.
125#[derive(Event, Debug, Clone)]
126pub struct KeepAliveEvent {
127    pub entity: Entity,
128    /// The ID of the keepalive. This is an arbitrary number, but vanilla
129    /// servers use the time to generate this.
130    pub id: u64,
131}
132
133#[derive(Event, Debug, Clone)]
134pub struct ResourcePackEvent {
135    pub entity: Entity,
136    /// The random ID for this request to download the resource pack. The packet
137    /// for replying to a resource pack push must contain the same ID.
138    pub id: Uuid,
139    pub url: String,
140    pub hash: String,
141    pub required: bool,
142    pub prompt: Option<FormattedText>,
143}
144
145/// An instance (aka world, dimension) was loaded by a client.
146///
147/// Since the instance is given to you as a weak reference, it won't be able to
148/// be `upgrade`d if all local players leave it.
149#[derive(Event, Debug, Clone)]
150pub struct InstanceLoadedEvent {
151    pub entity: Entity,
152    pub name: ResourceLocation,
153    pub instance: Weak<RwLock<Instance>>,
154}
155
156/// A Bevy trigger that's sent when our client receives a [`ClientboundPing`]
157/// packet in the game state.
158///
159/// Also see [`ConfigPingEvent`] which is used for the config state.
160///
161/// This is not an event and can't be listened to from a normal system,
162///so  `EventReader<PingEvent>` will not work.
163///
164/// To use it, add your "system" with `add_observer` instead of `add_systems`
165/// and use `Trigger<PingEvent>` instead of `EventReader`.
166///
167/// The client Entity that received the packet will be attached to the trigger.
168///
169/// [`ClientboundPing`]: azalea_protocol::packets::game::ClientboundPing
170/// [`ConfigPingEvent`]: crate::packet::config::ConfigPingEvent
171#[derive(Event, Debug, Clone)]
172pub struct PingEvent(pub azalea_protocol::packets::game::ClientboundPing);