azalea_client/plugins/
loading.rs

1use azalea_core::tick::GameTick;
2use azalea_entity::{HasClientLoaded, InLoadedChunk, LocalEntity, update_in_loaded_chunk};
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            // vanilla runs this on gameMode.tick()
16            player_loaded_packet
17                .after(update_in_loaded_chunk)
18                .before(PhysicsSet)
19                .before(MiningSet)
20                .before(crate::movement::send_position),
21        );
22    }
23}
24
25// this component is removed on respawn or disconnect
26// (notably, it's not removed on login)
27
28// mojmap interchangeably calls it 'has client loaded' and 'has player loaded',
29// i prefer the client one because it makes it clear that the component is only
30// present on our own clients
31
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}