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