azalea_client/plugins/
loading.rs

1use azalea_core::tick::GameTick;
2use azalea_entity::{InLoadedChunk, LocalEntity};
3use azalea_physics::PhysicsSet;
4use azalea_protocol::packets::game::ServerboundPlayerLoaded;
5use bevy_app::{App, Plugin};
6use bevy_ecs::prelude::*;
7
8use crate::{mining::MiningSet, packet::game::SendPacketEvent};
9
10pub struct PlayerLoadedPlugin;
11impl Plugin for PlayerLoadedPlugin {
12    fn build(&self, app: &mut App) {
13        app.add_systems(
14            GameTick,
15            player_loaded_packet
16                .after(PhysicsSet)
17                .before(MiningSet)
18                .before(crate::movement::send_position),
19        );
20    }
21}
22
23// this component is removed on respawn or disconnect
24// (notably, it's not removed on login)
25
26// mojmap interchangeably calls it 'has client loaded' and 'has player loaded',
27// i prefer the client one because it makes it clear that the component is only
28// present on our own clients
29
30#[derive(Component)]
31pub struct HasClientLoaded;
32#[allow(clippy::type_complexity)]
33pub fn player_loaded_packet(
34    mut commands: Commands,
35    query: Query<
36        Entity,
37        (
38            With<LocalEntity>,
39            Without<HasClientLoaded>,
40            // the vanilla client waits for the chunk mesh to be "compiled" for the renderer (or
41            // some other conditions) before sending PlayerLoaded. see LevelLoadStatusManager.tick
42            // in the decompiled source
43            With<InLoadedChunk>,
44        ),
45    >,
46) {
47    for entity in query.iter() {
48        commands.trigger(SendPacketEvent::new(entity, ServerboundPlayerLoaded));
49        commands.entity(entity).insert(HasClientLoaded);
50    }
51}