azalea/
lib.rs

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