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