azalea_client/plugins/
disconnect.rs1use azalea_chat::FormattedText;
4use azalea_entity::{
5 EntityBundle, HasClientLoaded, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle,
6};
7use azalea_world::MinecraftEntityId;
8use bevy_app::{App, Plugin, PostUpdate};
9use bevy_ecs::prelude::*;
10use derive_more::Deref;
11use tracing::info;
12
13use super::login::IsAuthenticated;
14use crate::{
15 chat_signing, client::JoinedClientBundle, connection::RawConnection,
16 local_player::InstanceHolder, tick_counter::TicksConnected,
17};
18
19pub struct DisconnectPlugin;
20impl Plugin for DisconnectPlugin {
21 fn build(&self, app: &mut App) {
22 app.add_event::<DisconnectEvent>().add_systems(
23 PostUpdate,
24 (
25 update_read_packets_task_running_component,
26 remove_components_from_disconnected_players,
27 disconnect_on_connection_dead,
32 )
33 .chain(),
34 );
35 }
36}
37
38#[derive(Event)]
49pub struct DisconnectEvent {
50 pub entity: Entity,
51 pub reason: Option<FormattedText>,
52}
53
54#[derive(Bundle)]
59pub struct RemoveOnDisconnectBundle {
60 pub joined_client: JoinedClientBundle,
61
62 pub entity: EntityBundle,
63 pub minecraft_entity_id: MinecraftEntityId,
64 pub instance_holder: InstanceHolder,
65 pub player_metadata: PlayerMetadataBundle,
66 pub in_loaded_chunk: InLoadedChunk,
67 pub raw_connection: RawConnection,
69 pub is_connection_alive: IsConnectionAlive,
71 pub chat_signing_session: chat_signing::ChatSigningSession,
73 pub is_authenticated: IsAuthenticated,
75 pub has_client_loaded: HasClientLoaded,
77 pub ticks_alive: TicksConnected,
79}
80
81pub fn remove_components_from_disconnected_players(
84 mut commands: Commands,
85 mut events: EventReader<DisconnectEvent>,
86 mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
87) {
88 for DisconnectEvent { entity, reason } in events.read() {
89 info!(
90 "A client {entity:?} was disconnected{}",
91 if let Some(reason) = reason {
92 format!(": {reason}")
93 } else {
94 "".to_string()
95 }
96 );
97 commands
98 .entity(*entity)
99 .remove::<RemoveOnDisconnectBundle>();
100 for mut loaded_by in &mut loaded_by_query.iter_mut() {
107 loaded_by.remove(entity);
108 }
109 }
110}
111
112#[derive(Component, Clone, Copy, Debug, Deref)]
113pub struct IsConnectionAlive(bool);
114
115fn update_read_packets_task_running_component(
116 query: Query<(Entity, &RawConnection)>,
117 mut commands: Commands,
118) {
119 for (entity, raw_connection) in &query {
120 let running = raw_connection.is_alive();
121 commands.entity(entity).insert(IsConnectionAlive(running));
122 }
123}
124
125#[allow(clippy::type_complexity)]
126fn disconnect_on_connection_dead(
127 query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
128 mut disconnect_events: EventWriter<DisconnectEvent>,
129) {
130 for (entity, &is_connection_alive) in &query {
131 if !*is_connection_alive {
132 disconnect_events.write(DisconnectEvent {
133 entity,
134 reason: None,
135 });
136 }
137 }
138}