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}