azalea_client/plugins/
disconnect.rs1use azalea_chat::FormattedText;
4use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
5use azalea_world::MinecraftEntityId;
6use bevy_app::{App, Plugin, PostUpdate};
7use bevy_ecs::prelude::*;
8use derive_more::Deref;
9use tracing::info;
10
11use super::login::IsAuthenticated;
12use crate::{InstanceHolder, chat_signing, client::JoinedClientBundle, connection::RawConnection};
13
14pub struct DisconnectPlugin;
15impl Plugin for DisconnectPlugin {
16 fn build(&self, app: &mut App) {
17 app.add_event::<DisconnectEvent>().add_systems(
18 PostUpdate,
19 (
20 update_read_packets_task_running_component,
21 remove_components_from_disconnected_players,
22 disconnect_on_connection_dead,
27 )
28 .chain(),
29 );
30 }
31}
32
33#[derive(Event)]
44pub struct DisconnectEvent {
45 pub entity: Entity,
46 pub reason: Option<FormattedText>,
47}
48
49#[derive(Bundle)]
54pub struct RemoveOnDisconnectBundle {
55 pub joined_client: JoinedClientBundle,
56 pub entity: EntityBundle,
57 pub minecraft_entity_id: MinecraftEntityId,
58 pub instance_holder: InstanceHolder,
59 pub player_metadata: PlayerMetadataBundle,
60 pub in_loaded_chunk: InLoadedChunk,
61 pub raw_connection: RawConnection,
63 pub is_connection_alive: IsConnectionAlive,
65 pub chat_signing_session: chat_signing::ChatSigningSession,
67 pub is_authenticated: IsAuthenticated,
69}
70
71pub fn remove_components_from_disconnected_players(
74 mut commands: Commands,
75 mut events: EventReader<DisconnectEvent>,
76 mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
77) {
78 for DisconnectEvent { entity, reason } in events.read() {
79 info!(
80 "A client {entity:?} was disconnected{}",
81 if let Some(reason) = reason {
82 format!(": {reason}")
83 } else {
84 "".to_string()
85 }
86 );
87 commands
88 .entity(*entity)
89 .remove::<RemoveOnDisconnectBundle>();
90 for mut loaded_by in &mut loaded_by_query.iter_mut() {
97 loaded_by.remove(entity);
98 }
99 }
100}
101
102#[derive(Component, Clone, Copy, Debug, Deref)]
103pub struct IsConnectionAlive(bool);
104
105fn update_read_packets_task_running_component(
106 query: Query<(Entity, &RawConnection)>,
107 mut commands: Commands,
108) {
109 for (entity, raw_connection) in &query {
110 let running = raw_connection.is_alive();
111 commands.entity(entity).insert(IsConnectionAlive(running));
112 }
113}
114
115#[allow(clippy::type_complexity)]
116fn disconnect_on_connection_dead(
117 query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
118 mut disconnect_events: EventWriter<DisconnectEvent>,
119) {
120 for (entity, &is_connection_alive) in &query {
121 if !*is_connection_alive {
122 disconnect_events.write(DisconnectEvent {
123 entity,
124 reason: None,
125 });
126 }
127 }
128}