azalea/
lib.rs

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