azalea/
lib.rs

1#![doc = include_str!("../README.md")]
2#![feature(type_changing_struct_update)]
3#![feature(never_type)]
4
5pub mod accept_resource_packs;
6pub mod auto_respawn;
7pub mod auto_tool;
8mod bot;
9pub mod container;
10pub mod nearest_entity;
11pub mod pathfinder;
12pub mod prelude;
13pub mod swarm;
14
15use std::{net::SocketAddr, time::Duration};
16
17use app::Plugins;
18pub use azalea_auth as auth;
19pub use azalea_block as blocks;
20pub use azalea_brigadier as brigadier;
21pub use azalea_buf as buf;
22pub use azalea_chat::FormattedText;
23pub use azalea_client::*;
24pub use azalea_core as core;
25// these are re-exported on this level because they're very common
26pub use azalea_core::{
27    position::{BlockPos, Vec3},
28    resource_location::ResourceLocation,
29};
30pub use azalea_entity as entity;
31pub use azalea_physics as physics;
32pub use azalea_protocol as protocol;
33pub use azalea_registry as registry;
34pub use azalea_world as world;
35pub use bevy_app as app;
36pub use bevy_ecs as ecs;
37pub use bot::*;
38use ecs::component::Component;
39use futures::{Future, future::BoxFuture};
40use protocol::{ServerAddress, connect::Proxy, resolver::ResolverError};
41use swarm::SwarmBuilder;
42use thiserror::Error;
43
44pub type BoxHandleFn<S, R> =
45    Box<dyn Fn(Client, azalea_client::Event, S) -> BoxFuture<'static, R> + Send>;
46pub type HandleFn<S, Fut> = fn(Client, azalea_client::Event, S) -> Fut;
47
48#[derive(Error, Debug)]
49pub enum StartError {
50    #[error("Invalid address")]
51    InvalidAddress,
52    #[error(transparent)]
53    ResolveAddress(#[from] ResolverError),
54}
55
56/// A builder for creating new [`Client`]s. This is the recommended way of
57/// making Azalea bots.
58///
59/// ```no_run
60/// # use azalea::prelude::*;
61/// # #[tokio::main(flavor = "current_thread")]
62/// # async fn main() {
63/// ClientBuilder::new()
64///     .set_handler(handle)
65///     .start(Account::offline("bot"), "localhost")
66///     .await;
67/// # }
68/// # #[derive(Component, Clone, Default)]
69/// # pub struct State;
70/// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
71/// #     Ok(())
72/// # }
73/// ```
74pub struct ClientBuilder<S, R>
75where
76    S: Default + Send + Sync + Clone + Component + 'static,
77    R: Send + 'static,
78    Self: Send,
79{
80    /// Internally, ClientBuilder is just a wrapper over SwarmBuilder since it's
81    /// technically just a subset of it so we can avoid duplicating code this
82    /// way.
83    swarm: SwarmBuilder<S, swarm::NoSwarmState, R, ()>,
84}
85impl ClientBuilder<NoState, ()> {
86    /// Start building a client that can join the world.
87    #[must_use]
88    pub fn new() -> Self {
89        Self::new_without_plugins()
90            .add_plugins(DefaultPlugins)
91            .add_plugins(DefaultBotPlugins)
92    }
93
94    /// [`Self::new`] but without adding the plugins by default.
95    ///
96    /// This is useful if you want to disable a default plugin. This also exists
97    /// for swarms, see [`SwarmBuilder::new_without_plugins`].
98    ///
99    /// Note that you can also disable `LogPlugin` by disabling the `log`
100    /// feature.
101    ///
102    /// You **must** add [`DefaultPlugins`] and [`DefaultBotPlugins`] to this.
103    ///
104    /// ```
105    /// # use azalea::prelude::*;
106    /// use azalea::app::PluginGroup;
107    ///
108    /// let client_builder = ClientBuilder::new_without_plugins()
109    ///     .add_plugins(
110    ///         azalea::DefaultPlugins
111    ///             .build()
112    ///             .disable::<azalea::chat_signing::ChatSigningPlugin>(),
113    ///     )
114    ///     .add_plugins(azalea::DefaultBotPlugins);
115    /// # client_builder.set_handler(handle);
116    /// # #[derive(Component, Clone, Default)]
117    /// # pub struct State;
118    /// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
119    /// #     Ok(())
120    /// # }
121    /// ```
122    #[must_use]
123    pub fn new_without_plugins() -> Self {
124        Self {
125            swarm: SwarmBuilder::new_without_plugins(),
126        }
127    }
128
129    /// Set the function that's called every time a bot receives an [`Event`].
130    /// This is the way to handle normal per-bot events.
131    ///
132    /// Currently, you can have up to one client handler.
133    ///
134    /// Note that if you're creating clients directly from the ECS using
135    /// [`StartJoinServerEvent`] and the client wasn't already in the ECS, then
136    /// the handler function won't be called for that client. This shouldn't be
137    /// a concern for most bots, though.
138    ///
139    /// ```
140    /// # use azalea::prelude::*;
141    /// # let client_builder = azalea::ClientBuilder::new();
142    /// client_builder.set_handler(handle);
143    ///
144    /// # #[derive(Component, Clone, Default)]
145    /// # pub struct State;
146    /// async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
147    ///     Ok(())
148    /// }
149    /// ```
150    ///
151    /// [`StartJoinServerEvent`]: azalea_client::join::StartJoinServerEvent
152    #[must_use]
153    pub fn set_handler<S, Fut, R>(self, handler: HandleFn<S, Fut>) -> ClientBuilder<S, R>
154    where
155        S: Default + Send + Sync + Clone + Component + 'static,
156        Fut: Future<Output = R> + Send + 'static,
157        R: Send + 'static,
158    {
159        ClientBuilder {
160            swarm: self.swarm.set_handler(handler),
161        }
162    }
163}
164impl<S, R> ClientBuilder<S, R>
165where
166    S: Default + Send + Sync + Clone + Component + 'static,
167    R: Send + 'static,
168{
169    /// Set the client state instead of initializing defaults.
170    #[must_use]
171    pub fn set_state(mut self, state: S) -> Self {
172        self.swarm.states = vec![state];
173        self
174    }
175    /// Add a group of plugins to the client.
176    ///
177    /// See [`Self::new_without_plugins`] to learn how to disable default
178    /// plugins.
179    #[must_use]
180    pub fn add_plugins<M>(mut self, plugins: impl Plugins<M>) -> Self {
181        self.swarm = self.swarm.add_plugins(plugins);
182        self
183    }
184
185    /// Configures the auto-reconnection behavior for our bot.
186    ///
187    /// If this is `Some`, then it'll set the default reconnection delay for our
188    /// bot (how long it'll wait after being kicked before it tries
189    /// rejoining). if it's `None`, then auto-reconnecting will be disabled.
190    ///
191    /// If this function isn't called, then our client will reconnect after
192    /// [`DEFAULT_RECONNECT_DELAY`].
193    ///
194    /// [`DEFAULT_RECONNECT_DELAY`]: azalea_client::auto_reconnect::DEFAULT_RECONNECT_DELAY
195    #[must_use]
196    pub fn reconnect_after(mut self, delay: impl Into<Option<Duration>>) -> Self {
197        self.swarm.reconnect_after = delay.into();
198        self
199    }
200
201    /// Build this `ClientBuilder` into an actual [`Client`] and join the given
202    /// server. If the client can't join, it'll keep retrying forever until it
203    /// can.
204    ///
205    /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
206    /// that implements `TryInto<ServerAddress>`.
207    ///
208    /// # Errors
209    ///
210    /// This will error if the given address is invalid or couldn't be resolved
211    /// to a Minecraft server.
212    ///
213    /// [`ServerAddress`]: azalea_protocol::ServerAddress
214    pub async fn start(
215        mut self,
216        account: Account,
217        address: impl TryInto<ServerAddress>,
218    ) -> Result<!, StartError> {
219        self.swarm.accounts = vec![(account, JoinOpts::default())];
220        if self.swarm.states.is_empty() {
221            self.swarm.states = vec![S::default()];
222        }
223        self.swarm.start(address).await
224    }
225
226    /// Do the same as [`Self::start`], but allow passing in custom join
227    /// options.
228    pub async fn start_with_opts(
229        mut self,
230        account: Account,
231        address: impl TryInto<ServerAddress>,
232        opts: JoinOpts,
233    ) -> Result<!, StartError> {
234        self.swarm.accounts = vec![(account, opts.clone())];
235        if self.swarm.states.is_empty() {
236            self.swarm.states = vec![S::default()];
237        }
238        self.swarm.start_with_default_opts(address, opts).await
239    }
240}
241impl Default for ClientBuilder<NoState, ()> {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247/// A marker that can be used in place of a State in [`ClientBuilder`] or
248/// [`SwarmBuilder`]. You probably don't need to use this manually since the
249/// compiler will infer it for you.
250///
251/// [`SwarmBuilder`]: swarm::SwarmBuilder
252#[derive(Component, Clone, Default)]
253pub struct NoState;
254
255/// Optional settings when adding an account to a swarm or client.
256#[derive(Clone, Debug, Default)]
257#[non_exhaustive]
258pub struct JoinOpts {
259    /// The Socks5 proxy that this bot will use.
260    pub proxy: Option<Proxy>,
261    /// Override the server address that this specific bot will send in the
262    /// handshake packet.
263    pub custom_address: Option<ServerAddress>,
264    /// Override the socket address that this specific bot will use to connect
265    /// to the server.
266    pub custom_resolved_address: Option<SocketAddr>,
267}
268
269impl JoinOpts {
270    pub fn new() -> Self {
271        Self::default()
272    }
273
274    pub fn update(&mut self, other: &Self) {
275        if let Some(proxy) = other.proxy.clone() {
276            self.proxy = Some(proxy);
277        }
278        if let Some(custom_address) = other.custom_address.clone() {
279            self.custom_address = Some(custom_address);
280        }
281        if let Some(custom_resolved_address) = other.custom_resolved_address {
282            self.custom_resolved_address = Some(custom_resolved_address);
283        }
284    }
285
286    /// Set the proxy that this bot will use.
287    #[must_use]
288    pub fn proxy(mut self, proxy: Proxy) -> Self {
289        self.proxy = Some(proxy);
290        self
291    }
292    /// Set the custom address that this bot will send in the handshake packet.
293    #[must_use]
294    pub fn custom_address(mut self, custom_address: ServerAddress) -> Self {
295        self.custom_address = Some(custom_address);
296        self
297    }
298    /// Set the custom resolved address that this bot will use to connect to the
299    /// server.
300    #[must_use]
301    pub fn custom_resolved_address(mut self, custom_resolved_address: SocketAddr) -> Self {
302        self.custom_resolved_address = Some(custom_resolved_address);
303        self
304    }
305}