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