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::{
13 chat_signing, client::JoinedClientBundle, connection::RawConnection, loading::HasClientLoaded,
14 local_player::InstanceHolder, tick_counter::TicksConnected,
15};
16
17pub struct DisconnectPlugin;
18impl Plugin for DisconnectPlugin {
19 fn build(&self, app: &mut App) {
20 app.add_event::<DisconnectEvent>().add_systems(
21 PostUpdate,
22 (
23 update_read_packets_task_running_component,
24 remove_components_from_disconnected_players,
25 disconnect_on_connection_dead,
30 )
31 .chain(),
32 );
33 }
34}
35
36#[derive(Event)]
47pub struct DisconnectEvent {
48 pub entity: Entity,
49 pub reason: Option<FormattedText>,
50}
51
52#[derive(Bundle)]
57pub struct RemoveOnDisconnectBundle {
58 pub joined_client: JoinedClientBundle,
59
60 pub entity: EntityBundle,
61 pub minecraft_entity_id: MinecraftEntityId,
62 pub instance_holder: InstanceHolder,
63 pub player_metadata: PlayerMetadataBundle,
64 pub in_loaded_chunk: InLoadedChunk,
65 pub raw_connection: RawConnection,
67 pub is_connection_alive: IsConnectionAlive,
69 pub chat_signing_session: chat_signing::ChatSigningSession,
71 pub is_authenticated: IsAuthenticated,
73 pub has_client_loaded: HasClientLoaded,
75 pub ticks_alive: TicksConnected,
77}
78
79pub fn remove_components_from_disconnected_players(
82 mut commands: Commands,
83 mut events: EventReader<DisconnectEvent>,
84 mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
85) {
86 for DisconnectEvent { entity, reason } in events.read() {
87 info!(
88 "A client {entity:?} was disconnected{}",
89 if let Some(reason) = reason {
90 format!(": {reason}")
91 } else {
92 "".to_string()
93 }
94 );
95 commands
96 .entity(*entity)
97 .remove::<RemoveOnDisconnectBundle>();
98 for mut loaded_by in &mut loaded_by_query.iter_mut() {
105 loaded_by.remove(entity);
106 }
107 }
108}
109
110#[derive(Component, Clone, Copy, Debug, Deref)]
111pub struct IsConnectionAlive(bool);
112
113fn update_read_packets_task_running_component(
114 query: Query<(Entity, &RawConnection)>,
115 mut commands: Commands,
116) {
117 for (entity, raw_connection) in &query {
118 let running = raw_connection.is_alive();
119 commands.entity(entity).insert(IsConnectionAlive(running));
120 }
121}
122
123#[allow(clippy::type_complexity)]
124fn disconnect_on_connection_dead(
125 query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
126 mut disconnect_events: EventWriter<DisconnectEvent>,
127) {
128 for (entity, &is_connection_alive) in &query {
129 if !*is_connection_alive {
130 disconnect_events.write(DisconnectEvent {
131 entity,
132 reason: None,
133 });
134 }
135 }
136}