Skip to main content

azalea/client_impl/
mod.rs

1use std::{collections::HashMap, sync::Arc};
2
3use azalea_auth::game_profile::GameProfile;
4use azalea_client::{
5    DefaultPlugins,
6    account::Account,
7    connection::RawConnection,
8    disconnect::DisconnectEvent,
9    join::{ConnectOpts, StartJoinServerEvent},
10    local_player::{Experience, Hunger, TabList, WorldHolder},
11    packet::game::SendGamePacketEvent,
12    player::{GameProfileComponent, PlayerInfo},
13    start_ecs_runner,
14    tick_counter::TicksConnected,
15};
16use azalea_core::{
17    data_registry::{DataRegistryWithKey, ResolvableDataRegistry},
18    entity_id::MinecraftEntityId,
19};
20use azalea_entity::indexing::{EntityIdIndex, EntityUuidIndex};
21use azalea_protocol::{
22    address::{ResolvableAddr, ResolvedAddr},
23    connect::Proxy,
24    packets::{Packet, game::ServerboundGamePacket},
25    resolve::ResolveError,
26};
27use azalea_registry::{DataRegistryKeyRef, identifier::Identifier};
28use azalea_world::{PartialWorld, World, WorldName};
29use bevy_app::{App, AppExit};
30use bevy_ecs::{entity::Entity, resource::Resource, world::Mut};
31use parking_lot::RwLock;
32use tokio::sync::{mpsc, oneshot};
33use uuid::Uuid;
34
35use crate::{
36    bot::DefaultBotPlugins,
37    client_impl::error::AzaleaResult,
38    entity_ref::EntityRef,
39    events::{Event, LocalPlayerEvents},
40    swarm::DefaultSwarmPlugins,
41};
42
43pub mod attack;
44pub mod chat;
45pub mod client_information;
46pub mod entity_query;
47pub mod error;
48pub mod interact;
49pub mod inventory;
50pub mod mining;
51pub mod movement;
52
53/// A Minecraft client instance that can interact with the world.
54///
55/// To make a new client, use either [`azalea::ClientBuilder`] or
56/// [`Client::join`].
57///
58/// Note that `Client` is inaccessible from systems (i.e. plugins), but you can
59/// achieve everything that client can do with ECS events.
60///
61/// [`azalea::ClientBuilder`]: https://docs.rs/azalea/latest/azalea/struct.ClientBuilder.html
62#[derive(Clone)]
63pub struct Client {
64    /// The entity for this client in the ECS.
65    pub entity: Entity,
66
67    /// A mutually exclusive reference to the entity component system (ECS).
68    ///
69    /// You probably don't need to access this directly. Note that if you're
70    /// using a shared world (i.e. a swarm), the ECS will also contain all
71    /// entities in all worlds.
72    ///
73    /// You can nearly always use [`Self::component`], [`Self::query_self`],
74    /// [`Self::query_entity`], or another one of those related functions to
75    /// access the ECS instead.
76    pub ecs: Arc<RwLock<bevy_ecs::world::World>>,
77}
78
79pub struct StartClientOpts {
80    pub ecs_lock: Arc<RwLock<bevy_ecs::world::World>>,
81    pub account: Account,
82    pub connect_opts: ConnectOpts,
83    pub event_sender: Option<mpsc::UnboundedSender<Event>>,
84}
85
86impl StartClientOpts {
87    pub fn new(
88        account: Account,
89        address: ResolvedAddr,
90        event_sender: Option<mpsc::UnboundedSender<Event>>,
91    ) -> StartClientOpts {
92        Self::new_with_appexit_rx(account, address, event_sender).0
93    }
94
95    pub fn new_with_appexit_rx(
96        account: Account,
97        address: ResolvedAddr,
98        event_sender: Option<mpsc::UnboundedSender<Event>>,
99    ) -> (StartClientOpts, oneshot::Receiver<AppExit>) {
100        let mut app = App::new();
101        app.add_plugins((DefaultPlugins, DefaultBotPlugins, DefaultSwarmPlugins));
102
103        let (ecs_lock, start_running_systems, appexit_rx) = start_ecs_runner(app.main_mut());
104        start_running_systems();
105
106        (
107            Self {
108                ecs_lock,
109                account,
110                connect_opts: ConnectOpts {
111                    address,
112                    server_proxy: None,
113                    sessionserver_proxy: None,
114                },
115                event_sender,
116            },
117            appexit_rx,
118        )
119    }
120
121    /// Configure the SOCKS5 proxy used for connecting to the server and for
122    /// authenticating with Mojang.
123    ///
124    /// To configure these separately, for example to only use the proxy for the
125    /// Minecraft server and not for authentication, you may use
126    /// [`Self::server_proxy`] and [`Self::sessionserver_proxy`] individually.
127    pub fn proxy(self, proxy: Proxy) -> Self {
128        self.server_proxy(proxy.clone()).sessionserver_proxy(proxy)
129    }
130    /// Configure the SOCKS5 proxy that will be used for connecting to the
131    /// Minecraft server.
132    ///
133    /// To avoid errors on servers with the "prevent-proxy-connections" option
134    /// set, you should usually use [`Self::proxy`] instead.
135    ///
136    /// Also see [`Self::sessionserver_proxy`].
137    pub fn server_proxy(mut self, proxy: Proxy) -> Self {
138        self.connect_opts.server_proxy = Some(proxy);
139        self
140    }
141    /// Configure the SOCKS5 proxy that this bot will use for authenticating the
142    /// server join with Mojang's API.
143    ///
144    /// Also see [`Self::proxy`] and [`Self::server_proxy`].
145    pub fn sessionserver_proxy(mut self, proxy: Proxy) -> Self {
146        self.connect_opts.sessionserver_proxy = Some(proxy);
147        self
148    }
149}
150
151impl Client {
152    /// Create a new client from the given [`GameProfile`], ECS Entity, ECS
153    /// World, and schedule runner function.
154    /// You should only use this if you want to change these fields from the
155    /// defaults, otherwise use [`Client::join`].
156    pub fn new(entity: Entity, ecs: Arc<RwLock<bevy_ecs::world::World>>) -> Self {
157        Self {
158            // default our id to 0, it'll be set later
159            entity,
160
161            ecs,
162        }
163    }
164
165    /// Connect to a Minecraft server.
166    ///
167    /// To change the render distance and other settings, use
168    /// [`Client::set_client_information`]. To watch for events like packets
169    /// sent by the server, use the `rx` variable this function returns.
170    ///
171    /// # Examples
172    ///
173    /// ```rust,no_run
174    /// use azalea::{Account, Client};
175    ///
176    /// #[tokio::main]
177    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
178    ///     let account = Account::offline("bot");
179    ///     let (client, rx) = Client::join(account, "localhost").await?;
180    ///     client.chat("Hello, world!");
181    ///     client.disconnect();
182    ///     Ok(())
183    /// }
184    /// ```
185    pub async fn join(
186        account: Account,
187        address: impl ResolvableAddr,
188    ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), ResolveError> {
189        let address = address.resolve().await?;
190        let (tx, rx) = mpsc::unbounded_channel();
191
192        let client = Self::start_client(StartClientOpts::new(account, address, Some(tx))).await;
193        Ok((client, rx))
194    }
195
196    pub async fn join_with_proxy(
197        account: Account,
198        address: impl ResolvableAddr,
199        proxy: Proxy,
200    ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), ResolveError> {
201        let address = address.resolve().await?;
202        let (tx, rx) = mpsc::unbounded_channel();
203
204        let client =
205            Self::start_client(StartClientOpts::new(account, address, Some(tx)).proxy(proxy)).await;
206        Ok((client, rx))
207    }
208
209    /// Create a [`Client`] when you already have the ECS made with
210    /// [`start_ecs_runner`]. You'd usually want to use [`Self::join`] instead.
211    pub async fn start_client(
212        StartClientOpts {
213            ecs_lock,
214            account,
215            connect_opts,
216            event_sender,
217        }: StartClientOpts,
218    ) -> Self {
219        // send a StartJoinServerEvent
220
221        let (start_join_callback_tx, mut start_join_callback_rx) =
222            mpsc::unbounded_channel::<Entity>();
223
224        ecs_lock.write().write_message(StartJoinServerEvent {
225            account,
226            connect_opts,
227            start_join_callback_tx: Some(start_join_callback_tx),
228        });
229
230        let entity = start_join_callback_rx.recv().await.expect(
231            "start_join_callback should not be dropped before sending a message, this is a bug in Azalea",
232        );
233
234        if let Some(event_sender) = event_sender {
235            ecs_lock
236                .write()
237                .entity_mut(entity)
238                .insert(LocalPlayerEvents(event_sender));
239        }
240
241        Client::new(entity, ecs_lock)
242    }
243
244    /// Write a packet directly to the server.
245    pub fn write_packet(&self, packet: impl Packet<ServerboundGamePacket>) {
246        let packet = packet.into_variant();
247        self.ecs
248            .write()
249            .commands()
250            .trigger(SendGamePacketEvent::new(self.entity, packet));
251    }
252
253    /// Disconnect this client from the server by ending all tasks.
254    ///
255    /// The OwnedReadHalf for the TCP connection is in one of the tasks, so it
256    /// automatically closes the connection when that's dropped.
257    ///
258    /// Note that this will not return from your client builder. If you need
259    /// that, consider using [`Self::exit`] instead.
260    pub fn disconnect(&self) {
261        self.ecs.write().write_message(DisconnectEvent {
262            entity: self.entity,
263            reason: None,
264        });
265    }
266
267    /// End the entire client or swarm, and return from
268    /// [`ClientBuilder::start`] or [`SwarmBuilder::start`].
269    ///
270    /// You should typically avoid calling this if you intend on creating the
271    /// client again, because creating an entirely new swarm can be a
272    /// relatively expensive process.
273    ///
274    /// If you only want to change the server that the bots are connecting to,
275    /// it may be better to access the client's internal [`Swarm`] and call
276    /// [`Swarm::add_with_opts`] with a different server address.
277    ///
278    /// For convenience, this is also duplicated on `Swarm` as [`Swarm::exit`].
279    ///
280    /// [`ClientBuilder::start`]: crate::ClientBuilder::start
281    /// [`SwarmBuilder::start`]: crate::swarm::SwarmBuilder::start
282    /// [`Swarm`]: crate::swarm::Swarm
283    /// [`Swarm::add_with_opts`]: crate::swarm::Swarm::add_with_opts
284    /// [`Swarm::exit`]: crate::swarm::Swarm::exit
285    ///
286    /// ```no_run
287    /// // a bot that joins a server and prints "done!" when it's disconnected or if it fails to connect.
288    /// use azalea::{NoState, prelude::*};
289    /// #[tokio::main]
290    /// async fn main() {
291    ///     let account = Account::offline("bot");
292    ///     ClientBuilder::new()
293    ///         .set_handler(handle)
294    ///         .start(account, "localhost")
295    ///         .await;
296    ///     println!("done!");
297    /// }
298    /// async fn handle(bot: Client, event: Event, _state: NoState) -> eyre::Result<()> {
299    ///     match event {
300    ///         Event::Disconnect(_) | Event::ConnectionFailed(_) => {
301    ///             bot.exit();
302    ///         }
303    ///         _ => {}
304    ///     }
305    ///     Ok(())
306    /// }
307    /// ```
308    pub fn exit(&self) {
309        self.ecs.write().write_message(AppExit::Success);
310    }
311
312    pub fn with_raw_connection<R>(&self, f: impl FnOnce(&RawConnection) -> R) -> AzaleaResult<R> {
313        self.query_self::<&RawConnection, _>(f)
314    }
315    pub fn with_raw_connection_mut<R>(
316        &self,
317        f: impl FnOnce(Mut<'_, RawConnection>) -> R,
318    ) -> AzaleaResult<R> {
319        self.query_self::<&mut RawConnection, _>(f)
320    }
321
322    /// Get a resource from the ECS. This will clone the resource and return it.
323    pub fn resource<T: Resource + Clone>(&self) -> T {
324        self.ecs.read().resource::<T>().clone()
325    }
326
327    /// Get a required ECS resource and call the given function with it.
328    pub fn map_resource<T: Resource, R>(&self, f: impl FnOnce(&T) -> R) -> R {
329        let ecs = self.ecs.read();
330        let value = ecs.resource::<T>();
331        f(value)
332    }
333
334    /// Get an optional ECS resource and call the given function with it.
335    pub fn map_get_resource<T: Resource, R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
336        let ecs = self.ecs.read();
337        let value = ecs.get_resource::<T>();
338        f(value)
339    }
340
341    /// Get an `RwLock` with a reference to our (potentially shared) Minecraft
342    /// world.
343    ///
344    /// This gets the [`World`] from the client's [`WorldHolder`]
345    /// component. If it's a normal client, then it'll be the same as the
346    /// world the client has loaded. If the client is using a shared world,
347    /// then the shared world will be a superset of the client's world.
348    pub fn world(&self) -> AzaleaResult<Arc<RwLock<World>>> {
349        let world_holder = self.component::<WorldHolder>()?;
350        Ok(world_holder.shared.clone())
351    }
352
353    /// Get an `RwLock` with a reference to the world that this client has
354    /// loaded.
355    ///
356    /// ```
357    /// # use azalea_core::position::ChunkPos;
358    /// # fn example(client: &azalea::Client) -> azalea::error::AzaleaResult<()> {
359    /// let world = client.partial_world()?;
360    /// let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some();
361    /// # Ok(())
362    /// # }
363    pub fn partial_world(&self) -> AzaleaResult<Arc<RwLock<PartialWorld>>> {
364        let world_holder = self.component::<WorldHolder>()?;
365        Ok(world_holder.partial.clone())
366    }
367
368    /// Returns whether we have a received the login packet yet.
369    pub fn logged_in(&self) -> bool {
370        // the login packet tells us the world name
371        self.query_self::<&WorldName, _>(|_| {}).is_ok()
372    }
373
374    /// Returns the client as an [`EntityRef`], allowing you to treat it as any
375    /// other entity.
376    pub fn entity(&self) -> EntityRef {
377        self.entity_ref_for(self.entity)
378    }
379
380    /// Create an [`EntityRef`] for the given ECS entity.
381    pub fn entity_ref_for(&self, entity: Entity) -> EntityRef {
382        EntityRef::new(self.clone(), entity)
383    }
384}
385
386impl Client {
387    /// Get the hunger level of this client, which includes both food and
388    /// saturation.
389    ///
390    /// This is a shortcut for `self.component::<Hunger>()?.to_owned()`.
391    pub fn hunger(&self) -> AzaleaResult<Hunger> {
392        Ok(self.component::<Hunger>()?.to_owned())
393    }
394
395    /// Get the experience of this client.
396    ///
397    /// This is a shortcut for `self.component::<Experience>().to_owned()`.
398    pub fn experience(&self) -> AzaleaResult<Experience> {
399        Ok(self.component::<Experience>()?.to_owned())
400    }
401
402    /// Get the username of this client's account.
403    ///
404    /// This is a shortcut for `bot.account().username().to_owned()`.
405    pub fn username(&self) -> String {
406        self.account().username().to_owned()
407    }
408    /// Get the username of this client, as sent to us by the server.
409    ///
410    /// This is a shortcut for `bot.profile()?.name.to_owned()`.
411    ///
412    /// In some cases, this may be different from [`Self::username`] if the
413    /// server sends us a [`GameProfile`] with a mismatching username. Using
414    /// [`Self::username`] is recommended if you're not sure which one to use.
415    pub fn server_username(&self) -> AzaleaResult<String> {
416        Ok(self.profile()?.name.to_owned())
417    }
418
419    /// Get the Minecraft UUID of this client's account.
420    ///
421    /// This is a shortcut for `**self.component::<EntityUuid>()`.
422    pub fn uuid(&self) -> Uuid {
423        self.account().uuid()
424    }
425
426    /// Get a map of player UUIDs to their information in the tab list.
427    ///
428    /// This is a shortcut for `*bot.component::<TabList>()`.
429    pub fn tab_list(&self) -> AzaleaResult<HashMap<Uuid, PlayerInfo>> {
430        Ok((**self.component::<TabList>()?).clone())
431    }
432
433    /// Returns the [`GameProfile`] for our client. This contains your username,
434    /// UUID, and skin data.
435    ///
436    /// These values are set by the server upon login, which means they might
437    /// not match up with your actual game profile. Also, note that the username
438    /// and skin that gets displayed in-game will actually be the ones from
439    /// the tab list, which you can get from [`Self::tab_list`].
440    ///
441    /// This as also available from the ECS as [`GameProfileComponent`].
442    pub fn profile(&self) -> AzaleaResult<GameProfile> {
443        Ok((**self.component::<GameProfileComponent>()?).clone())
444    }
445
446    /// Returns the [`Account`] for our client.
447    pub fn account(&self) -> Account {
448        self.component::<Account>()
449            .expect(
450                "clients cannot exist without an Account, and Account isn't removed from clients",
451            )
452            .clone()
453    }
454
455    /// A convenience function to get the Minecraft Uuid of a player by their
456    /// username, if they're present in the tab list.
457    ///
458    /// You can chain this with [`Client::entity_by_uuid`] to get the ECS
459    /// `Entity` for the player.
460    pub fn player_uuid_by_username(&self, username: &str) -> AzaleaResult<Option<Uuid>> {
461        Ok(self
462            .tab_list()?
463            .values()
464            .find(|player| player.profile.name == username)
465            .map(|player| player.profile.uuid))
466    }
467
468    /// Get an [`Entity`] in the world by its Minecraft UUID, if it's within
469    /// render distance.
470    ///
471    /// Also see [`Self::entity_by_uuid`] and
472    /// [`Self::entity_id_by_minecraft_id`].
473    pub fn entity_id_by_uuid(&self, uuid: Uuid) -> Option<Entity> {
474        self.map_resource::<EntityUuidIndex, _>(|entity_uuid_index| entity_uuid_index.get(&uuid))
475    }
476    /// Get an [`EntityRef`] in the world by its Minecraft UUID, if it's within
477    /// render distance.
478    ///
479    /// Also see [`Self::entity_id_by_uuid`].
480    pub fn entity_by_uuid(&self, uuid: Uuid) -> Option<EntityRef> {
481        self.entity_id_by_uuid(uuid).map(|e| self.entity_ref_for(e))
482    }
483
484    /// Get an [`Entity`] in the world by its [`MinecraftEntityId`].
485    ///
486    /// Also see [`Self::entity_by_uuid`] and [`Self::entity_id_by_uuid`].
487    pub fn entity_id_by_minecraft_id(&self, id: MinecraftEntityId) -> AzaleaResult<Option<Entity>> {
488        self.query_self::<&EntityIdIndex, _>(|entity_id_index| {
489            entity_id_index.get_by_minecraft_entity(id)
490        })
491    }
492    /// Get an [`EntityRef`] in the world by its [`MinecraftEntityId`].
493    ///
494    /// Also see [`Self::entity_id_by_uuid`].
495    pub fn entity_by_minecraft_id(&self, id: MinecraftEntityId) -> AzaleaResult<Option<EntityRef>> {
496        Ok(self
497            .entity_id_by_minecraft_id(id)?
498            .map(|e| EntityRef::new(self.clone(), e)))
499    }
500
501    /// Call the given function with the client's [`RegistryHolder`].
502    ///
503    /// Note that the player's world will be locked during this time, which may
504    /// result in a deadlock if you try to access the world again while
505    /// in the function.
506    ///
507    /// [`RegistryHolder`]: azalea_core::registry_holder::RegistryHolder
508    pub fn with_registry_holder<R>(
509        &self,
510        f: impl FnOnce(&azalea_core::registry_holder::RegistryHolder) -> R,
511    ) -> AzaleaResult<R> {
512        let world = self.world()?;
513        let registries = &world.read().registries;
514        Ok(f(registries))
515    }
516
517    /// Resolve the given registry to its name.
518    ///
519    /// This is necessary for data-driven registries like [`Enchantment`].
520    ///
521    /// [`Enchantment`]: azalea_registry::data::Enchantment
522    #[deprecated = "use `bot.resolve_registry_key(registry).map(|r| r.into_ident())` instead."]
523    pub fn resolve_registry_name(
524        &self,
525        registry: &impl ResolvableDataRegistry,
526    ) -> AzaleaResult<Option<Identifier>> {
527        self.with_registry_holder(|registries| registry.key(registries).map(|r| r.into_ident()))
528    }
529
530    /// Resolve the given registry entry to its key (aka name).
531    ///
532    /// This is necessary for data-driven registries like [`Enchantment`] and
533    /// [`Biome`](azalea_registry::data::Biome).
534    ///
535    /// To get the key as an [`Identifier`], you can map the return value like
536    /// `.map(|r| r.into_ident())`.
537    ///
538    /// [`Enchantment`]: azalea_registry::data::Enchantment
539    pub fn resolve_registry_key<R: ResolvableDataRegistry>(
540        &self,
541        registry: &R,
542    ) -> AzaleaResult<Option<R::Key>> {
543        self.with_registry_holder(|registries| registry.key_owned(registries))
544    }
545
546    /// Resolve the given registry to its name and data and call the given
547    /// function with it.
548    ///
549    /// This is necessary for data-driven registries like [`Enchantment`].
550    ///
551    /// If you just want the value name, use [`Self::resolve_registry_name`]
552    /// instead.
553    ///
554    /// [`Enchantment`]: azalea_registry::data::Enchantment
555    pub fn with_resolved_registry<R: ResolvableDataRegistry, Ret>(
556        &self,
557        registry: R,
558        f: impl FnOnce(&Identifier, &R::DeserializesTo) -> Ret,
559    ) -> AzaleaResult<Option<Ret>> {
560        self.with_registry_holder(|registries| {
561            registry
562                .resolve(registries)
563                .map(|(name, data)| f(name, data))
564        })
565    }
566
567    /// Returns the number of ticks since the `login` packet was received, or 0
568    /// if the client isn't in the world.
569    ///
570    /// This is a shortcut for getting the [`TicksConnected`] component.
571    pub fn ticks_connected(&self) -> u64 {
572        self.component::<TicksConnected>().map(|c| c.0).unwrap_or(0)
573    }
574}