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