azalea_client/
disconnect.rs

1//! Disconnect a client from the server.
2
3use azalea_chat::FormattedText;
4use azalea_entity::LocalEntity;
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::{client::JoinedClientBundle, events::LocalPlayerEvents, raw_connection::RawConnection};
19
20pub struct DisconnectPlugin;
21impl Plugin for DisconnectPlugin {
22    fn build(&self, app: &mut App) {
23        app.add_event::<DisconnectEvent>().add_systems(
24            PostUpdate,
25            (
26                update_read_packets_task_running_component,
27                disconnect_on_connection_dead,
28                remove_components_from_disconnected_players,
29            )
30                .chain(),
31        );
32    }
33}
34
35/// An event sent when a client is getting disconnected.
36#[derive(Event)]
37pub struct DisconnectEvent {
38    pub entity: Entity,
39    pub reason: Option<FormattedText>,
40}
41
42/// System that removes the [`JoinedClientBundle`] from the entity when it
43/// receives a [`DisconnectEvent`].
44pub fn remove_components_from_disconnected_players(
45    mut commands: Commands,
46    mut events: EventReader<DisconnectEvent>,
47) {
48    for DisconnectEvent { entity, .. } in events.read() {
49        trace!("Got DisconnectEvent for {entity:?}");
50        commands
51            .entity(*entity)
52            .remove::<JoinedClientBundle>()
53            // this makes it close the tcp connection
54            .remove::<RawConnection>()
55            // swarm detects when this tx gets dropped to fire SwarmEvent::Disconnect
56            .remove::<LocalPlayerEvents>();
57        // note that we don't remove the client from the ECS, so if they decide
58        // to reconnect they'll keep their state
59    }
60}
61
62#[derive(Component, Clone, Copy, Debug, Deref)]
63pub struct IsConnectionAlive(bool);
64
65fn update_read_packets_task_running_component(
66    query: Query<(Entity, &RawConnection)>,
67    mut commands: Commands,
68) {
69    for (entity, raw_connection) in &query {
70        let running = raw_connection.is_alive();
71        commands.entity(entity).insert(IsConnectionAlive(running));
72    }
73}
74
75#[allow(clippy::type_complexity)]
76fn disconnect_on_connection_dead(
77    query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
78    mut disconnect_events: EventWriter<DisconnectEvent>,
79) {
80    for (entity, &is_connection_alive) in &query {
81        if !*is_connection_alive {
82            disconnect_events.send(DisconnectEvent {
83                entity,
84                reason: None,
85            });
86        }
87    }
88}