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}