aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/plugins/disconnect.rs
blob: bd10ac75045ebd6063747403f293190571b3242d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Disconnect a client from the server.

use azalea_chat::FormattedText;
use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::{
    component::Component,
    entity::Entity,
    event::{EventReader, EventWriter},
    prelude::Event,
    query::{Changed, With},
    schedule::IntoSystemConfigs,
    system::{Commands, Query},
};
use derive_more::Deref;
use tracing::trace;

use crate::{
    InstanceHolder, client::JoinedClientBundle, events::LocalPlayerEvents,
    raw_connection::RawConnection,
};

pub struct DisconnectPlugin;
impl Plugin for DisconnectPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<DisconnectEvent>().add_systems(
            PostUpdate,
            (
                update_read_packets_task_running_component,
                disconnect_on_connection_dead,
                remove_components_from_disconnected_players,
            )
                .chain(),
        );
    }
}

/// An event sent when a client is getting disconnected.
#[derive(Event)]
pub struct DisconnectEvent {
    pub entity: Entity,
    pub reason: Option<FormattedText>,
}

/// A system that removes the several components from our clients when they get
/// a [`DisconnectEvent`].
pub fn remove_components_from_disconnected_players(
    mut commands: Commands,
    mut events: EventReader<DisconnectEvent>,
    mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
) {
    for DisconnectEvent { entity, .. } in events.read() {
        trace!("Got DisconnectEvent for {entity:?}");
        commands
            .entity(*entity)
            .remove::<JoinedClientBundle>()
            .remove::<EntityBundle>()
            .remove::<InstanceHolder>()
            .remove::<PlayerMetadataBundle>()
            .remove::<InLoadedChunk>()
            // this makes it close the tcp connection
            .remove::<RawConnection>()
            // swarm detects when this tx gets dropped to fire SwarmEvent::Disconnect
            .remove::<LocalPlayerEvents>();
        // note that we don't remove the client from the ECS, so if they decide
        // to reconnect they'll keep their state

        // now we have to remove ourselves from the LoadedBy for every entity.
        // in theory this could be inefficient if we have massive swarms... but in
        // practice this is fine.
        for mut loaded_by in &mut loaded_by_query.iter_mut() {
            loaded_by.remove(entity);
        }
    }
}

#[derive(Component, Clone, Copy, Debug, Deref)]
pub struct IsConnectionAlive(bool);

fn update_read_packets_task_running_component(
    query: Query<(Entity, &RawConnection)>,
    mut commands: Commands,
) {
    for (entity, raw_connection) in &query {
        let running = raw_connection.is_alive();
        commands.entity(entity).insert(IsConnectionAlive(running));
    }
}

#[allow(clippy::type_complexity)]
fn disconnect_on_connection_dead(
    query: Query<(Entity, &IsConnectionAlive), (Changed<IsConnectionAlive>, With<LocalEntity>)>,
    mut disconnect_events: EventWriter<DisconnectEvent>,
) {
    for (entity, &is_connection_alive) in &query {
        if !*is_connection_alive {
            disconnect_events.send(DisconnectEvent {
                entity,
                reason: None,
            });
        }
    }
}