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;
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
64 if let Ok((mut raw_connection, in_game_state)) = query.get_mut(event.sent_by) {
65 if in_game_state.is_none() {
66 error!(
67 "Tried to send a game packet {:?} while not in game state",
68 event.packet
69 );
70 return;
71 }
72
73 // debug!("Sending game packet: {:?}", event.packet);
74 if let Err(e) = raw_connection.write(event.packet.clone()) {
75 error!("Failed to send packet: {e}");
76 }
77 }
78}
79
80/// A system that converts [`SendPacketEvent`] events into triggers so they get
81/// received by [`handle_outgoing_packets_observer`].
82pub fn handle_outgoing_packets(mut commands: Commands, mut events: EventReader<SendPacketEvent>) {
83 for event in events.read() {
84 commands.trigger(event.clone());
85 }
86}
87
88/// A player joined the game (or more specifically, was added to the tab
89/// list of a local player).
90#[derive(Event, Debug, Clone)]
91pub struct AddPlayerEvent {
92 /// The local player entity that received this event.
93 pub entity: Entity,
94 pub info: PlayerInfo,
95}
96/// A player left the game (or maybe is still in the game and was just
97/// removed from the tab list of a local player).
98#[derive(Event, Debug, Clone)]
99pub struct RemovePlayerEvent {
100 /// The local player entity that received this event.
101 pub entity: Entity,
102 pub info: PlayerInfo,
103}
104/// A player was updated in the tab list of a local player (gamemode, display
105/// name, or latency changed).
106#[derive(Event, Debug, Clone)]
107pub struct UpdatePlayerEvent {
108 /// The local player entity that received this event.
109 pub entity: Entity,
110 pub info: PlayerInfo,
111}
112
113/// Event for when an entity dies. dies. If it's a local player and there's a
114/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
115/// be included.
116#[derive(Event, Debug, Clone)]
117pub struct DeathEvent {
118 pub entity: Entity,
119 pub packet: Option<ClientboundPlayerCombatKill>,
120}
121
122/// A KeepAlive packet is sent from the server to verify that the client is
123/// still connected.
124#[derive(Event, Debug, Clone)]
125pub struct KeepAliveEvent {
126 pub entity: Entity,
127 /// The ID of the keepalive. This is an arbitrary number, but vanilla
128 /// servers use the time to generate this.
129 pub id: u64,
130}
131
132#[derive(Event, Debug, Clone)]
133pub struct ResourcePackEvent {
134 pub entity: Entity,
135 /// The random ID for this request to download the resource pack. The packet
136 /// for replying to a resource pack push must contain the same ID.
137 pub id: Uuid,
138 pub url: String,
139 pub hash: String,
140 pub required: bool,
141 pub prompt: Option<FormattedText>,
142}
143
144/// An instance (aka world, dimension) was loaded by a client.
145///
146/// Since the instance is given to you as a weak reference, it won't be able to
147/// be `upgrade`d if all local players leave it.
148#[derive(Event, Debug, Clone)]
149pub struct InstanceLoadedEvent {
150 pub entity: Entity,
151 pub name: ResourceLocation,
152 pub instance: Weak<RwLock<Instance>>,
153}
154
155/// A Bevy trigger that's sent when our client receives a [`ClientboundPing`]
156/// packet in the game state.
157///
158/// Also see [`ConfigPingEvent`] which is used for the config state.
159///
160/// This is not an event and can't be listened to from a normal system,
161///so `EventReader<PingEvent>` will not work.
162///
163/// To use it, add your "system" with `add_observer` instead of `add_systems`
164/// and use `Trigger<PingEvent>` instead of `EventReader`.
165///
166/// The client Entity that received the packet will be attached to the trigger.
167///
168/// [`ClientboundPing`]: azalea_protocol::packets::game::ClientboundPing
169/// [`ConfigPingEvent`]: crate::packet::config::ConfigPingEvent
170#[derive(Event, Debug, Clone)]
171pub struct PingEvent(pub azalea_protocol::packets::game::ClientboundPing);