azalea_client/plugins/
disconnect.rs1use azalea_chat::FormattedText;
4use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
5use bevy_app::{App, Plugin, PostUpdate};
6use bevy_ecs::{
7 component::Component,
8 entity::Entity,
9 event::{EventReader, EventWriter},
10 prelude::Event,
11 query::{Changed, With},
12 schedule::IntoSystemConfigs,
13 system::{Commands, Query},
14};
15use derive_more::Deref;
16use tracing::trace;
17
18use crate::{
19 InstanceHolder, client::JoinedClientBundle, events::LocalPlayerEvents,
20 raw_connection::RawConnection,
21};
22
23pub struct DisconnectPlugin;
24impl Plugin for DisconnectPlugin {
25 fn build(&self, app: &mut App) {
26 app.add_event::<DisconnectEvent>().add_systems(
27 PostUpdate,
28 (
29 update_read_packets_task_running_component,
30 disconnect_on_connection_dead,
31 remove_components_from_disconnected_players,
32 )
33 .chain(),
34 );
35 }
36}
37
38#[derive(Event)]
40pub struct DisconnectEvent {
41 pub entity: Entity,
42 pub reason: Option<FormattedText>,
43}
44
45pub fn remove_components_from_disconnected_players(
48 mut commands: Commands,
49 mut events: EventReader<DisconnectEvent>,
50 mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
51) {
52 for DisconnectEvent { entity, .. } in events.read() {
53 trace!("Got DisconnectEvent for {entity:?}");
54 commands
55 .entity(*entity)
56 .remove::<JoinedClientBundle>()
57 .remove::<EntityBundle>()
58 .remove::<InstanceHolder>()
59 .remove::<PlayerMetadataBundle>()
60 .remove::<InLoadedChunk>()
61 .remove::<RawConnection>()
63 .remove::<LocalPlayerEvents>();
65 for mut loaded_by in &mut loaded_by_query.iter_mut() {
72 loaded_by.remove(entity);
73 }
74 }
75}
76
77#[derive(Component, Clone, Copy, Debug, Deref)]
78pub struct IsConnectionAlive(bool);
79
80fn update_read_packets_task_running_component(
81 query: Query<(Entity, &RawConnection)>,
82 mut commands: Commands,
83) {
84 for (entity, raw_connection) in &query {
85 let running = raw_connection.is_alive();
86 commands.entity(entity).insert(IsConnectionAlive(running));
87 }
88}
89
90#[allow(clippy::type_complexity)]
91fn disconnect_on_connection_dead(
92 query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
93 mut disconnect_events: EventWriter<DisconnectEvent>,
94) {
95 for (entity, &is_connection_alive) in &query {
96 if !*is_connection_alive {
97 disconnect_events.send(DisconnectEvent {
98 entity,
99 reason: None,
100 });
101 }
102 }
103}