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