azalea_client/
client.rs

1use std::{
2    collections::HashMap,
3    fmt::Debug,
4    io,
5    net::SocketAddr,
6    sync::Arc,
7    thread,
8    time::{Duration, Instant},
9};
10
11use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
12use azalea_chat::FormattedText;
13use azalea_core::{
14    data_registry::ResolvableDataRegistry, position::Vec3, resource_location::ResourceLocation,
15    tick::GameTick,
16};
17use azalea_entity::{
18    EntityPlugin, EntityUpdateSet, EyeHeight, LocalEntity, Position,
19    indexing::{EntityIdIndex, EntityUuidIndex},
20    metadata::Health,
21};
22use azalea_physics::PhysicsPlugin;
23use azalea_protocol::{
24    ServerAddress,
25    common::client_information::ClientInformation,
26    connect::{Connection, ConnectionError, Proxy},
27    packets::{
28        self, ClientIntention, ConnectionProtocol, PROTOCOL_VERSION, Packet,
29        config::{ClientboundConfigPacket, ServerboundConfigPacket},
30        game::ServerboundGamePacket,
31        handshake::{
32            ClientboundHandshakePacket, ServerboundHandshakePacket,
33            s_intention::ServerboundIntention,
34        },
35        login::{
36            ClientboundLoginPacket, s_hello::ServerboundHello, s_key::ServerboundKey,
37            s_login_acknowledged::ServerboundLoginAcknowledged,
38        },
39    },
40    resolver,
41};
42use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
43use bevy_app::{App, Plugin, PluginGroup, PluginGroupBuilder, PluginsState, Update};
44use bevy_ecs::{
45    bundle::Bundle,
46    component::Component,
47    entity::Entity,
48    schedule::{InternedScheduleLabel, IntoSystemConfigs, LogLevel, ScheduleBuildSettings},
49    system::Resource,
50    world::World,
51};
52use bevy_time::TimePlugin;
53use parking_lot::{Mutex, RwLock};
54use simdnbt::owned::NbtCompound;
55use thiserror::Error;
56use tokio::{
57    sync::mpsc::{self, error::TrySendError},
58    time,
59};
60use tracing::{debug, error, info};
61use uuid::Uuid;
62
63use crate::{
64    Account, PlayerInfo,
65    attack::{self, AttackPlugin},
66    brand::BrandPlugin,
67    chat::ChatPlugin,
68    chunks::{ChunkBatchInfo, ChunksPlugin},
69    disconnect::{DisconnectEvent, DisconnectPlugin},
70    events::{Event, EventsPlugin, LocalPlayerEvents},
71    interact::{CurrentSequenceNumber, InteractPlugin},
72    inventory::{Inventory, InventoryPlugin},
73    local_player::{
74        GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList,
75    },
76    mining::{self, MiningPlugin},
77    movement::{LastSentLookDirection, MovementPlugin, PhysicsState},
78    packet::{
79        PacketPlugin,
80        login::{self, InLoginState, LoginSendPacketQueue},
81    },
82    player::retroactively_add_game_profile_component,
83    pong::PongPlugin,
84    raw_connection::RawConnection,
85    respawn::RespawnPlugin,
86    task_pool::TaskPoolPlugin,
87    tick_broadcast::TickBroadcastPlugin,
88    tick_end::TickEndPlugin,
89};
90
91/// `Client` has the things that a user interacting with the library will want.
92///
93/// To make a new client, use either [`azalea::ClientBuilder`] or
94/// [`Client::join`].
95///
96/// Note that `Client` is inaccessible from systems (i.e. plugins), but you can
97/// achieve everything that client can do with events.
98///
99/// [`azalea::ClientBuilder`]: https://docs.rs/azalea/latest/azalea/struct.ClientBuilder.html
100#[derive(Clone)]
101pub struct Client {
102    /// The [`GameProfile`] for our client. This contains your username, UUID,
103    /// and skin data.
104    ///
105    /// This is immutable; the server cannot change it. To get the username and
106    /// skin the server chose for you, get your player from the [`TabList`]
107    /// component.
108    ///
109    /// This as also available from the ECS as [`GameProfileComponent`].
110    pub profile: GameProfile,
111    /// The entity for this client in the ECS.
112    pub entity: Entity,
113
114    /// The entity component system. You probably don't need to access this
115    /// directly. Note that if you're using a shared world (i.e. a swarm), this
116    /// will contain all entities in all worlds.
117    pub ecs: Arc<Mutex<World>>,
118
119    /// Use this to force the client to run the schedule outside of a tick.
120    pub run_schedule_sender: mpsc::Sender<()>,
121}
122
123/// An error that happened while joining the server.
124#[derive(Error, Debug)]
125pub enum JoinError {
126    #[error("{0}")]
127    Resolver(#[from] resolver::ResolverError),
128    #[error("{0}")]
129    Connection(#[from] ConnectionError),
130    #[error("{0}")]
131    ReadPacket(#[from] Box<azalea_protocol::read::ReadPacketError>),
132    #[error("{0}")]
133    Io(#[from] io::Error),
134    #[error("{0}")]
135    SessionServer(#[from] azalea_auth::sessionserver::ClientSessionServerError),
136    #[error("The given address could not be parsed into a ServerAddress")]
137    InvalidAddress,
138    #[error("Couldn't refresh access token: {0}")]
139    Auth(#[from] azalea_auth::AuthError),
140    #[error("Disconnected: {reason}")]
141    Disconnect { reason: FormattedText },
142}
143
144pub struct StartClientOpts<'a> {
145    pub ecs_lock: Arc<Mutex<World>>,
146    pub account: &'a Account,
147    pub address: &'a ServerAddress,
148    pub resolved_address: &'a SocketAddr,
149    pub proxy: Option<Proxy>,
150    pub run_schedule_sender: mpsc::Sender<()>,
151    pub event_sender: Option<mpsc::UnboundedSender<Event>>,
152}
153
154impl<'a> StartClientOpts<'a> {
155    pub fn new(
156        account: &'a Account,
157        address: &'a ServerAddress,
158        resolved_address: &'a SocketAddr,
159        event_sender: Option<mpsc::UnboundedSender<Event>>,
160    ) -> StartClientOpts<'a> {
161        // An event that causes the schedule to run. This is only used internally.
162        let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
163
164        let mut app = App::new();
165        app.add_plugins(DefaultPlugins);
166
167        let ecs_lock = start_ecs_runner(app, run_schedule_receiver, run_schedule_sender.clone());
168
169        Self {
170            ecs_lock,
171            account,
172            address,
173            resolved_address,
174            proxy: None,
175            run_schedule_sender,
176            event_sender,
177        }
178    }
179
180    pub fn proxy(mut self, proxy: Proxy) -> Self {
181        self.proxy = Some(proxy);
182        self
183    }
184}
185
186impl Client {
187    /// Create a new client from the given [`GameProfile`], ECS Entity, ECS
188    /// World, and schedule runner function.
189    /// You should only use this if you want to change these fields from the
190    /// defaults, otherwise use [`Client::join`].
191    pub fn new(
192        profile: GameProfile,
193        entity: Entity,
194        ecs: Arc<Mutex<World>>,
195        run_schedule_sender: mpsc::Sender<()>,
196    ) -> Self {
197        Self {
198            profile,
199            // default our id to 0, it'll be set later
200            entity,
201
202            ecs,
203
204            run_schedule_sender,
205        }
206    }
207
208    /// Connect to a Minecraft server.
209    ///
210    /// To change the render distance and other settings, use
211    /// [`Client::set_client_information`]. To watch for events like packets
212    /// sent by the server, use the `rx` variable this function returns.
213    ///
214    /// # Examples
215    ///
216    /// ```rust,no_run
217    /// use azalea_client::{Client, Account};
218    ///
219    /// #[tokio::main]
220    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
221    ///     let account = Account::offline("bot");
222    ///     let (client, rx) = Client::join(&account, "localhost").await?;
223    ///     client.chat("Hello, world!");
224    ///     client.disconnect();
225    ///     Ok(())
226    /// }
227    /// ```
228    pub async fn join(
229        account: &Account,
230        address: impl TryInto<ServerAddress>,
231    ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
232        let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
233        let resolved_address = resolver::resolve_address(&address).await?;
234        let (tx, rx) = mpsc::unbounded_channel();
235
236        let client = Self::start_client(StartClientOpts::new(
237            account,
238            &address,
239            &resolved_address,
240            Some(tx),
241        ))
242        .await?;
243        Ok((client, rx))
244    }
245
246    pub async fn join_with_proxy(
247        account: &Account,
248        address: impl TryInto<ServerAddress>,
249        proxy: Proxy,
250    ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
251        let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
252        let resolved_address = resolver::resolve_address(&address).await?;
253        let (tx, rx) = mpsc::unbounded_channel();
254
255        let client = Self::start_client(
256            StartClientOpts::new(account, &address, &resolved_address, Some(tx)).proxy(proxy),
257        )
258        .await?;
259        Ok((client, rx))
260    }
261
262    /// Create a [`Client`] when you already have the ECS made with
263    /// [`start_ecs_runner`]. You'd usually want to use [`Self::join`] instead.
264    pub async fn start_client(
265        StartClientOpts {
266            ecs_lock,
267            account,
268            address,
269            resolved_address,
270            proxy,
271            run_schedule_sender,
272            event_sender,
273        }: StartClientOpts<'_>,
274    ) -> Result<Self, JoinError> {
275        // check if an entity with our uuid already exists in the ecs and if so then
276        // just use that
277        let entity = {
278            let mut ecs = ecs_lock.lock();
279
280            let entity_uuid_index = ecs.resource::<EntityUuidIndex>();
281            let uuid = account.uuid_or_offline();
282            let entity = if let Some(entity) = entity_uuid_index.get(&account.uuid_or_offline()) {
283                debug!("Reusing entity {entity:?} for client");
284                entity
285            } else {
286                let entity = ecs.spawn_empty().id();
287                debug!("Created new entity {entity:?} for client");
288                // add to the uuid index
289                let mut entity_uuid_index = ecs.resource_mut::<EntityUuidIndex>();
290                entity_uuid_index.insert(uuid, entity);
291                entity
292            };
293
294            // add the Account to the entity now so plugins can access it earlier
295            ecs.entity_mut(entity).insert(account.to_owned());
296
297            entity
298        };
299
300        let conn = if let Some(proxy) = proxy {
301            Connection::new_with_proxy(resolved_address, proxy).await?
302        } else {
303            Connection::new(resolved_address).await?
304        };
305        let (conn, game_profile) =
306            Self::handshake(ecs_lock.clone(), entity, conn, account, address).await?;
307
308        // note that we send the proper packets in
309        // crate::configuration::handle_in_configuration_state
310
311        let (read_conn, write_conn) = conn.into_split();
312        let (read_conn, write_conn) = (read_conn.raw, write_conn.raw);
313
314        // we did the handshake, so now we're connected to the server
315
316        let mut ecs = ecs_lock.lock();
317
318        // we got the ConfigurationConnection, so the client is now connected :)
319        let client = Client::new(
320            game_profile.clone(),
321            entity,
322            ecs_lock.clone(),
323            run_schedule_sender.clone(),
324        );
325
326        let instance = Instance::default();
327        let instance_holder = crate::local_player::InstanceHolder::new(
328            entity,
329            // default to an empty world, it'll be set correctly later when we
330            // get the login packet
331            Arc::new(RwLock::new(instance)),
332        );
333
334        let mut entity = ecs.entity_mut(entity);
335        entity.insert((
336            // these stay when we switch to the game state
337            LocalPlayerBundle {
338                raw_connection: RawConnection::new(
339                    run_schedule_sender,
340                    ConnectionProtocol::Configuration,
341                    read_conn,
342                    write_conn,
343                ),
344                game_profile: GameProfileComponent(game_profile),
345                client_information: crate::ClientInformation::default(),
346                instance_holder,
347                metadata: azalea_entity::metadata::PlayerMetadataBundle::default(),
348            },
349            InConfigState,
350            // this component is never removed
351            LocalEntity,
352        ));
353        if let Some(event_sender) = event_sender {
354            // this is optional so we don't leak memory in case the user
355            entity.insert(LocalPlayerEvents(event_sender));
356        }
357
358        Ok(client)
359    }
360
361    /// Do a handshake with the server and get to the game state from the
362    /// initial handshake state.
363    ///
364    /// This will also automatically refresh the account's access token if
365    /// it's expired.
366    pub async fn handshake(
367        ecs_lock: Arc<Mutex<World>>,
368        entity: Entity,
369        mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
370        account: &Account,
371        address: &ServerAddress,
372    ) -> Result<
373        (
374            Connection<ClientboundConfigPacket, ServerboundConfigPacket>,
375            GameProfile,
376        ),
377        JoinError,
378    > {
379        // handshake
380        conn.write(ServerboundIntention {
381            protocol_version: PROTOCOL_VERSION,
382            hostname: address.host.clone(),
383            port: address.port,
384            intention: ClientIntention::Login,
385        })
386        .await?;
387        let mut conn = conn.login();
388
389        // this makes it so plugins can send an `SendLoginPacketEvent` event to the ecs
390        // and we'll send it to the server
391        let (ecs_packets_tx, mut ecs_packets_rx) = mpsc::unbounded_channel();
392        ecs_lock.lock().entity_mut(entity).insert((
393            LoginSendPacketQueue { tx: ecs_packets_tx },
394            crate::packet::login::IgnoreQueryIds::default(),
395            InLoginState,
396        ));
397
398        // login
399        conn.write(ServerboundHello {
400            name: account.username.clone(),
401            // TODO: pretty sure this should generate an offline-mode uuid instead of just
402            // Uuid::default()
403            profile_id: account.uuid.unwrap_or_default(),
404        })
405        .await?;
406
407        let (conn, profile) = loop {
408            let packet = tokio::select! {
409                packet = conn.read() => packet?,
410                Some(packet) = ecs_packets_rx.recv() => {
411                    // write this packet to the server
412                    conn.write(packet).await?;
413                    continue;
414                }
415            };
416
417            ecs_lock.lock().send_event(login::LoginPacketEvent {
418                entity,
419                packet: Arc::new(packet.clone()),
420            });
421
422            match packet {
423                ClientboundLoginPacket::Hello(p) => {
424                    debug!("Got encryption request");
425                    let Ok(e) = azalea_crypto::encrypt(&p.public_key, &p.challenge) else {
426                        error!("Failed to encrypt the challenge from the server for {p:?}");
427                        continue;
428                    };
429
430                    if let Some(access_token) = &account.access_token {
431                        // keep track of the number of times we tried
432                        // authenticating so we can give up after too many
433                        let mut attempts: usize = 1;
434
435                        while let Err(e) = {
436                            let access_token = access_token.lock().clone();
437                            conn.authenticate(
438                                &access_token,
439                                &account
440                                    .uuid
441                                    .expect("Uuid must be present if access token is present."),
442                                e.secret_key,
443                                &p,
444                            )
445                            .await
446                        } {
447                            if attempts >= 2 {
448                                // if this is the second attempt and we failed
449                                // both times, give up
450                                return Err(e.into());
451                            }
452                            if matches!(
453                                e,
454                                ClientSessionServerError::InvalidSession
455                                    | ClientSessionServerError::ForbiddenOperation
456                            ) {
457                                // uh oh, we got an invalid session and have
458                                // to reauthenticate now
459                                account.refresh().await?;
460                            } else {
461                                return Err(e.into());
462                            }
463                            attempts += 1;
464                        }
465                    }
466
467                    conn.write(ServerboundKey {
468                        key_bytes: e.encrypted_public_key,
469                        encrypted_challenge: e.encrypted_challenge,
470                    })
471                    .await?;
472
473                    conn.set_encryption_key(e.secret_key);
474                }
475                ClientboundLoginPacket::LoginCompression(p) => {
476                    debug!("Got compression request {:?}", p.compression_threshold);
477                    conn.set_compression_threshold(p.compression_threshold);
478                }
479                ClientboundLoginPacket::LoginFinished(p) => {
480                    debug!(
481                        "Got profile {:?}. handshake is finished and we're now switching to the configuration state",
482                        p.game_profile
483                    );
484                    conn.write(ServerboundLoginAcknowledged {}).await?;
485
486                    break (conn.config(), p.game_profile);
487                }
488                ClientboundLoginPacket::LoginDisconnect(p) => {
489                    debug!("Got disconnect {:?}", p);
490                    return Err(JoinError::Disconnect { reason: p.reason });
491                }
492                ClientboundLoginPacket::CustomQuery(p) => {
493                    debug!("Got custom query {:?}", p);
494                    // replying to custom query is done in
495                    // packet::login::process_packet_events
496                }
497                ClientboundLoginPacket::CookieRequest(p) => {
498                    debug!("Got cookie request {:?}", p);
499
500                    conn.write(packets::login::ServerboundCookieResponse {
501                        key: p.key,
502                        // cookies aren't implemented
503                        payload: None,
504                    })
505                    .await?;
506                }
507            }
508        };
509
510        ecs_lock
511            .lock()
512            .entity_mut(entity)
513            .remove::<login::IgnoreQueryIds>()
514            .remove::<LoginSendPacketQueue>()
515            .remove::<InLoginState>();
516
517        Ok((conn, profile))
518    }
519
520    /// Write a packet directly to the server.
521    pub fn write_packet(
522        &self,
523        packet: impl Packet<ServerboundGamePacket>,
524    ) -> Result<(), crate::raw_connection::WritePacketError> {
525        let packet = packet.into_variant();
526        self.raw_connection_mut(&mut self.ecs.lock())
527            .write_packet(packet)
528    }
529
530    /// Disconnect this client from the server by ending all tasks.
531    ///
532    /// The OwnedReadHalf for the TCP connection is in one of the tasks, so it
533    /// automatically closes the connection when that's dropped.
534    pub fn disconnect(&self) {
535        self.ecs.lock().send_event(DisconnectEvent {
536            entity: self.entity,
537            reason: None,
538        });
539    }
540
541    pub fn raw_connection<'a>(&'a self, ecs: &'a mut World) -> &'a RawConnection {
542        self.query::<&RawConnection>(ecs)
543    }
544    pub fn raw_connection_mut<'a>(
545        &'a self,
546        ecs: &'a mut World,
547    ) -> bevy_ecs::world::Mut<'a, RawConnection> {
548        self.query::<&mut RawConnection>(ecs)
549    }
550
551    /// Get a component from this client. This will clone the component and
552    /// return it.
553    ///
554    ///
555    /// If the component can't be cloned, try [`Self::map_component`] instead.
556    /// If it isn't guaranteed to be present, use [`Self::get_component`] or
557    /// [`Self::map_get_component`].
558    ///
559    /// You may also use [`Self::ecs`] and [`Self::query`] directly if you need
560    /// more control over when the ECS is locked.
561    ///
562    /// # Panics
563    ///
564    /// This will panic if the component doesn't exist on the client.
565    ///
566    /// # Examples
567    ///
568    /// ```
569    /// # use azalea_world::InstanceName;
570    /// # fn example(client: &azalea_client::Client) {
571    /// let world_name = client.component::<InstanceName>();
572    /// # }
573    pub fn component<T: Component + Clone>(&self) -> T {
574        self.query::<&T>(&mut self.ecs.lock()).clone()
575    }
576
577    /// Get a component from this client, or `None` if it doesn't exist.
578    ///
579    /// If the component can't be cloned, try [`Self::map_component`] instead.
580    /// You may also have to use [`Self::ecs`] and [`Self::query`] directly.
581    pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
582        self.query::<Option<&T>>(&mut self.ecs.lock()).cloned()
583    }
584
585    /// Get a resource from the ECS. This will clone the resource and return it.
586    pub fn resource<T: Resource + Clone>(&self) -> T {
587        self.ecs.lock().resource::<T>().clone()
588    }
589
590    /// Get a required ECS resource and call the given function with it.
591    pub fn map_resource<T: Resource, R>(&self, f: impl FnOnce(&T) -> R) -> R {
592        let ecs = self.ecs.lock();
593        let value = ecs.resource::<T>();
594        f(value)
595    }
596
597    /// Get an optional ECS resource and call the given function with it.
598    pub fn map_get_resource<T: Resource, R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
599        let ecs = self.ecs.lock();
600        let value = ecs.get_resource::<T>();
601        f(value)
602    }
603
604    /// Get a required component for this client and call the given function.
605    ///
606    /// Similar to [`Self::component`], but doesn't clone the component since
607    /// it's passed as a reference. [`Self::ecs`] will remain locked while the
608    /// callback is being run.
609    ///
610    /// If the component is not guaranteed to be present, use
611    /// [`Self::get_component`] instead.
612    ///
613    /// # Panics
614    ///
615    /// This will panic if the component doesn't exist on the client.
616    ///
617    /// ```
618    /// # use azalea_client::{Client, Hunger};
619    /// # fn example(bot: &Client) {
620    /// let hunger = bot.map_component::<Hunger, _>(|h| h.food);
621    /// # }
622    /// ```
623    pub fn map_component<T: Component, R>(&self, f: impl FnOnce(&T) -> R) -> R {
624        let mut ecs = self.ecs.lock();
625        let value = self.query::<&T>(&mut ecs);
626        f(value)
627    }
628
629    /// Optionally get a component for this client and call the given function.
630    ///
631    /// Similar to [`Self::get_component`], but doesn't clone the component
632    /// since it's passed as a reference. [`Self::ecs`] will remain locked
633    /// while the callback is being run.
634    ///
635    /// ```
636    /// # use azalea_client::{Client, mining::Mining};
637    /// # fn example(bot: &Client) {
638    /// let is_mining = bot.map_get_component::<Mining, _>(|m| m.is_some());
639    /// # }
640    /// ```
641    pub fn map_get_component<T: Component, R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
642        let mut ecs = self.ecs.lock();
643        let value = self.query::<Option<&T>>(&mut ecs);
644        f(value)
645    }
646
647    /// Get an `RwLock` with a reference to our (potentially shared) world.
648    ///
649    /// This gets the [`Instance`] from the client's [`InstanceHolder`]
650    /// component. If it's a normal client, then it'll be the same as the
651    /// world the client has loaded. If the client is using a shared world,
652    /// then the shared world will be a superset of the client's world.
653    pub fn world(&self) -> Arc<RwLock<Instance>> {
654        let instance_holder = self.component::<InstanceHolder>();
655        instance_holder.instance.clone()
656    }
657
658    /// Get an `RwLock` with a reference to the world that this client has
659    /// loaded.
660    ///
661    /// ```
662    /// # use azalea_core::position::ChunkPos;
663    /// # fn example(client: &azalea_client::Client) {
664    /// let world = client.partial_world();
665    /// let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some();
666    /// # }
667    pub fn partial_world(&self) -> Arc<RwLock<PartialInstance>> {
668        let instance_holder = self.component::<InstanceHolder>();
669        instance_holder.partial_instance.clone()
670    }
671
672    /// Returns whether we have a received the login packet yet.
673    pub fn logged_in(&self) -> bool {
674        // the login packet tells us the world name
675        self.query::<Option<&InstanceName>>(&mut self.ecs.lock())
676            .is_some()
677    }
678
679    /// Tell the server we changed our game options (i.e. render distance, main
680    /// hand). If this is not set before the login packet, the default will
681    /// be sent.
682    ///
683    /// ```rust,no_run
684    /// # use azalea_client::{Client, ClientInformation};
685    /// # async fn example(bot: Client) -> Result<(), Box<dyn std::error::Error>> {
686    /// bot.set_client_information(ClientInformation {
687    ///     view_distance: 2,
688    ///     ..Default::default()
689    /// })
690    /// .await?;
691    /// # Ok(())
692    /// # }
693    /// ```
694    pub async fn set_client_information(
695        &self,
696        client_information: ClientInformation,
697    ) -> Result<(), crate::raw_connection::WritePacketError> {
698        {
699            let mut ecs = self.ecs.lock();
700            let mut client_information_mut = self.query::<&mut ClientInformation>(&mut ecs);
701            *client_information_mut = client_information.clone();
702        }
703
704        if self.logged_in() {
705            debug!(
706                "Sending client information (already logged in): {:?}",
707                client_information
708            );
709            self.write_packet(azalea_protocol::packets::game::s_client_information::ServerboundClientInformation { information: client_information.clone() })?;
710        }
711
712        Ok(())
713    }
714}
715
716impl Client {
717    /// Get the position of this client.
718    ///
719    /// This is a shortcut for `Vec3::from(&bot.component::<Position>())`.
720    ///
721    /// Note that this value is given a default of [`Vec3::ZERO`] when it
722    /// receives the login packet, its true position may be set ticks
723    /// later.
724    pub fn position(&self) -> Vec3 {
725        Vec3::from(
726            &self
727                .get_component::<Position>()
728                .expect("the client's position hasn't been initialized yet"),
729        )
730    }
731
732    /// Get the position of this client's eyes.
733    ///
734    /// This is a shortcut for
735    /// `bot.position().up(bot.component::<EyeHeight>())`.
736    pub fn eye_position(&self) -> Vec3 {
737        self.position().up((*self.component::<EyeHeight>()) as f64)
738    }
739
740    /// Get the health of this client.
741    ///
742    /// This is a shortcut for `*bot.component::<Health>()`.
743    pub fn health(&self) -> f32 {
744        *self.component::<Health>()
745    }
746
747    /// Get the hunger level of this client, which includes both food and
748    /// saturation.
749    ///
750    /// This is a shortcut for `self.component::<Hunger>().to_owned()`.
751    pub fn hunger(&self) -> Hunger {
752        self.component::<Hunger>().to_owned()
753    }
754
755    /// Get the username of this client.
756    ///
757    /// This is a shortcut for
758    /// `bot.component::<GameProfileComponent>().name.to_owned()`.
759    pub fn username(&self) -> String {
760        self.component::<GameProfileComponent>().name.to_owned()
761    }
762
763    /// Get the Minecraft UUID of this client.
764    ///
765    /// This is a shortcut for `bot.component::<GameProfileComponent>().uuid`.
766    pub fn uuid(&self) -> Uuid {
767        self.component::<GameProfileComponent>().uuid
768    }
769
770    /// Get a map of player UUIDs to their information in the tab list.
771    ///
772    /// This is a shortcut for `*bot.component::<TabList>()`.
773    pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo> {
774        (*self.component::<TabList>()).clone()
775    }
776
777    /// A convenience function to get the Minecraft Uuid of a player by their
778    /// username, if they're present in the tab list.
779    ///
780    /// You can chain this with [`Client::entity_by_uuid`] to get the ECS
781    /// `Entity` for the player.
782    pub fn player_uuid_by_username(&self, username: &str) -> Option<Uuid> {
783        self.tab_list()
784            .values()
785            .find(|player| player.profile.name == username)
786            .map(|player| player.profile.uuid)
787    }
788
789    /// Get an ECS `Entity` in the world by its Minecraft UUID, if it's within
790    /// render distance.
791    pub fn entity_by_uuid(&self, uuid: Uuid) -> Option<Entity> {
792        self.map_resource::<EntityUuidIndex, _>(|entity_uuid_index| entity_uuid_index.get(&uuid))
793    }
794
795    /// Convert an ECS `Entity` to a [`MinecraftEntityId`].
796    pub fn minecraft_entity_by_ecs_entity(&self, entity: Entity) -> Option<MinecraftEntityId> {
797        self.map_component::<EntityIdIndex, _>(|entity_id_index| {
798            entity_id_index.get_by_ecs_entity(entity)
799        })
800    }
801    /// Convert a [`MinecraftEntityId`] to an ECS `Entity`.
802    pub fn ecs_entity_by_minecraft_entity(&self, entity: MinecraftEntityId) -> Option<Entity> {
803        self.map_component::<EntityIdIndex, _>(|entity_id_index| {
804            entity_id_index.get_by_minecraft_entity(entity)
805        })
806    }
807
808    /// Call the given function with the client's [`RegistryHolder`].
809    ///
810    /// The player's instance (aka world) will be locked during this time, which
811    /// may result in a deadlock if you try to access the instance again while
812    /// in the function.
813    ///
814    /// [`RegistryHolder`]: azalea_core::registry_holder::RegistryHolder
815    pub fn with_registry_holder<R>(
816        &self,
817        f: impl FnOnce(&azalea_core::registry_holder::RegistryHolder) -> R,
818    ) -> R {
819        let instance = self.world();
820        let registries = &instance.read().registries;
821        f(registries)
822    }
823
824    /// Resolve the given registry to its name.
825    ///
826    /// This is necessary for data-driven registries like [`Enchantment`].
827    ///
828    /// [`Enchantment`]: azalea_registry::Enchantment
829    pub fn resolve_registry_name(
830        &self,
831        registry: &impl ResolvableDataRegistry,
832    ) -> Option<ResourceLocation> {
833        self.with_registry_holder(|registries| registry.resolve_name(registries))
834    }
835    /// Resolve the given registry to its name and data and call the given
836    /// function with it.
837    ///
838    /// This is necessary for data-driven registries like [`Enchantment`].
839    ///
840    /// If you just want the value name, use [`Self::resolve_registry_name`]
841    /// instead.
842    ///
843    /// [`Enchantment`]: azalea_registry::Enchantment
844    pub fn with_resolved_registry<R>(
845        &self,
846        registry: impl ResolvableDataRegistry,
847        f: impl FnOnce(&ResourceLocation, &NbtCompound) -> R,
848    ) -> Option<R> {
849        self.with_registry_holder(|registries| {
850            registry
851                .resolve(registries)
852                .map(|(name, data)| f(name, data))
853        })
854    }
855}
856
857/// The bundle of components that's shared when we're either in the
858/// `configuration` or `game` state.
859///
860/// For the components that are only present in the `game` state, see
861/// [`JoinedClientBundle`].
862#[derive(Bundle)]
863pub struct LocalPlayerBundle {
864    pub raw_connection: RawConnection,
865    pub game_profile: GameProfileComponent,
866    pub client_information: ClientInformation,
867    pub instance_holder: InstanceHolder,
868
869    pub metadata: azalea_entity::metadata::PlayerMetadataBundle,
870}
871
872/// A bundle for the components that are present on a local player that is
873/// currently in the `game` protocol state. If you want to filter for this, use
874/// [`InGameState`].
875#[derive(Bundle, Default)]
876pub struct JoinedClientBundle {
877    // note that InstanceHolder isn't here because it's set slightly before we fully join the world
878    pub physics_state: PhysicsState,
879    pub inventory: Inventory,
880    pub tab_list: TabList,
881    pub current_sequence_number: CurrentSequenceNumber,
882    pub last_sent_direction: LastSentLookDirection,
883    pub abilities: PlayerAbilities,
884    pub permission_level: PermissionLevel,
885    pub chunk_batch_info: ChunkBatchInfo,
886    pub hunger: Hunger,
887
888    pub entity_id_index: EntityIdIndex,
889
890    pub mining: mining::MineBundle,
891    pub attack: attack::AttackBundle,
892
893    pub in_game_state: InGameState,
894}
895
896/// A marker component for local players that are currently in the
897/// `game` state.
898#[derive(Component, Clone, Debug, Default)]
899pub struct InGameState;
900/// A marker component for local players that are currently in the
901/// `configuration` state.
902#[derive(Component, Clone, Debug, Default)]
903pub struct InConfigState;
904
905pub struct AzaleaPlugin;
906impl Plugin for AzaleaPlugin {
907    fn build(&self, app: &mut App) {
908        app.add_systems(
909            Update,
910            (
911                // add GameProfileComponent when we get an AddPlayerEvent
912                retroactively_add_game_profile_component.after(EntityUpdateSet::Index),
913            ),
914        )
915        .init_resource::<InstanceContainer>()
916        .init_resource::<TabList>();
917    }
918}
919
920/// Start running the ECS loop!
921///
922/// You can create your app with `App::new()`, but don't forget to add
923/// [`DefaultPlugins`].
924#[doc(hidden)]
925pub fn start_ecs_runner(
926    mut app: App,
927    run_schedule_receiver: mpsc::Receiver<()>,
928    run_schedule_sender: mpsc::Sender<()>,
929) -> Arc<Mutex<World>> {
930    // this block is based on Bevy's default runner:
931    // https://github.com/bevyengine/bevy/blob/390877cdae7a17095a75c8f9f1b4241fe5047e83/crates/bevy_app/src/schedule_runner.rs#L77-L85
932    if app.plugins_state() != PluginsState::Cleaned {
933        // Wait for plugins to load
934        if app.plugins_state() == PluginsState::Adding {
935            info!("Waiting for plugins to load ...");
936            while app.plugins_state() == PluginsState::Adding {
937                thread::yield_now();
938            }
939        }
940        // Finish adding plugins and cleanup
941        app.finish();
942        app.cleanup();
943    }
944
945    // all resources should have been added by now so we can take the ecs from the
946    // app
947    let ecs = Arc::new(Mutex::new(std::mem::take(app.world_mut())));
948
949    tokio::spawn(run_schedule_loop(
950        ecs.clone(),
951        *app.main().update_schedule.as_ref().unwrap(),
952        run_schedule_receiver,
953    ));
954    tokio::spawn(tick_run_schedule_loop(run_schedule_sender));
955
956    ecs
957}
958
959async fn run_schedule_loop(
960    ecs: Arc<Mutex<World>>,
961    outer_schedule_label: InternedScheduleLabel,
962    mut run_schedule_receiver: mpsc::Receiver<()>,
963) {
964    let mut last_tick: Option<Instant> = None;
965    loop {
966        // whenever we get an event from run_schedule_receiver, run the schedule
967        run_schedule_receiver.recv().await;
968
969        let mut ecs = ecs.lock();
970
971        // if last tick is None or more than 50ms ago, run the GameTick schedule
972        ecs.run_schedule(outer_schedule_label);
973        if last_tick
974            .map(|last_tick| last_tick.elapsed() > Duration::from_millis(50))
975            .unwrap_or(true)
976        {
977            if let Some(last_tick) = &mut last_tick {
978                *last_tick += Duration::from_millis(50);
979            } else {
980                last_tick = Some(Instant::now());
981            }
982            ecs.run_schedule(GameTick);
983        }
984
985        ecs.clear_trackers();
986    }
987}
988
989/// Send an event to run the schedule every 50 milliseconds. It will stop when
990/// the receiver is dropped.
991pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::Sender<()>) {
992    let mut game_tick_interval = time::interval(Duration::from_millis(50));
993    // TODO: Minecraft bursts up to 10 ticks and then skips, we should too
994    game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
995
996    loop {
997        game_tick_interval.tick().await;
998        if let Err(TrySendError::Closed(())) = run_schedule_sender.try_send(()) {
999            error!("tick_run_schedule_loop failed because run_schedule_sender was closed");
1000            // the sender is closed so end the task
1001            return;
1002        }
1003    }
1004}
1005
1006pub struct AmbiguityLoggerPlugin;
1007impl Plugin for AmbiguityLoggerPlugin {
1008    fn build(&self, app: &mut App) {
1009        app.edit_schedule(Update, |schedule| {
1010            schedule.set_build_settings(ScheduleBuildSettings {
1011                ambiguity_detection: LogLevel::Warn,
1012                ..Default::default()
1013            });
1014        });
1015        app.edit_schedule(GameTick, |schedule| {
1016            schedule.set_build_settings(ScheduleBuildSettings {
1017                ambiguity_detection: LogLevel::Warn,
1018                ..Default::default()
1019            });
1020        });
1021    }
1022}
1023
1024/// This plugin group will add all the default plugins necessary for Azalea to
1025/// work.
1026pub struct DefaultPlugins;
1027
1028impl PluginGroup for DefaultPlugins {
1029    fn build(self) -> PluginGroupBuilder {
1030        #[allow(unused_mut)]
1031        let mut group = PluginGroupBuilder::start::<Self>()
1032            .add(AmbiguityLoggerPlugin)
1033            .add(TimePlugin)
1034            .add(PacketPlugin)
1035            .add(AzaleaPlugin)
1036            .add(EntityPlugin)
1037            .add(PhysicsPlugin)
1038            .add(EventsPlugin)
1039            .add(TaskPoolPlugin::default())
1040            .add(InventoryPlugin)
1041            .add(ChatPlugin)
1042            .add(DisconnectPlugin)
1043            .add(MovementPlugin)
1044            .add(InteractPlugin)
1045            .add(RespawnPlugin)
1046            .add(MiningPlugin)
1047            .add(AttackPlugin)
1048            .add(ChunksPlugin)
1049            .add(TickEndPlugin)
1050            .add(BrandPlugin)
1051            .add(TickBroadcastPlugin)
1052            .add(PongPlugin);
1053        #[cfg(feature = "log")]
1054        {
1055            group = group.add(bevy_log::LogPlugin::default());
1056        }
1057        group
1058    }
1059}