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