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