Skip to main content

azalea_client/plugins/packet/game/
events.rs

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