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;
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::BoxFuture, Future};
41use protocol::connect::Proxy;
42use protocol::{resolver::ResolverError, ServerAddress};
43use swarm::SwarmBuilder;
44use thiserror::Error;
45
46pub type BoxHandleFn<S, R> = Box<dyn Fn(Client, azalea_client::Event, S) -> BoxFuture<'static, R>>;
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{
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. This is useful
95    /// if you want to disable a default plugin.
96    ///
97    /// Note that you can also disable `LogPlugin` by disabling the `log`
98    /// feature.
99    ///
100    /// You **must** add [`DefaultPlugins`] and [`DefaultBotPlugins`] to this.
101    ///
102    /// ```
103    /// # use azalea::prelude::*;
104    /// use azalea::{app::PluginGroup, DefaultBotPlugins, DefaultPlugins};
105    /// use bevy_log::LogPlugin;
106    ///
107    /// let client_builder = ClientBuilder::new_without_plugins()
108    ///     .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
109    ///     .add_plugins(DefaultBotPlugins);
110    /// # client_builder.set_handler(handle);
111    /// # #[derive(Component, Clone, Default)]
112    /// # pub struct State;
113    /// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
114    /// #     Ok(())
115    /// # }
116    /// ```
117    #[must_use]
118    pub fn new_without_plugins() -> Self {
119        Self {
120            swarm: SwarmBuilder::new_without_plugins(),
121        }
122    }
123
124    /// Set the function that's called every time a bot receives an [`Event`].
125    /// This is the way to handle normal per-bot events.
126    ///
127    /// Currently you can have up to one client handler.
128    ///
129    /// ```
130    /// # use azalea::prelude::*;
131    /// # let client_builder = azalea::ClientBuilder::new();
132    /// client_builder.set_handler(handle);
133    ///
134    /// # #[derive(Component, Clone, Default)]
135    /// # pub struct State;
136    /// async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
137    ///     Ok(())
138    /// }
139    /// ```
140    #[must_use]
141    pub fn set_handler<S, Fut, R>(self, handler: HandleFn<S, Fut>) -> ClientBuilder<S, R>
142    where
143        S: Default + Send + Sync + Clone + Component + 'static,
144        Fut: Future<Output = R> + Send + 'static,
145        R: Send + 'static,
146    {
147        ClientBuilder {
148            swarm: self.swarm.set_handler(handler),
149        }
150    }
151}
152impl<S, R> ClientBuilder<S, R>
153where
154    S: Default + Send + Sync + Clone + Component + 'static,
155    R: Send + 'static,
156{
157    /// Set the client state instead of initializing defaults.
158    #[must_use]
159    pub fn set_state(mut self, state: S) -> Self {
160        self.swarm.states = vec![state];
161        self
162    }
163    /// Add a group of plugins to the client.
164    #[must_use]
165    pub fn add_plugins<M>(mut self, plugins: impl Plugins<M>) -> Self {
166        self.swarm = self.swarm.add_plugins(plugins);
167        self
168    }
169
170    /// Build this `ClientBuilder` into an actual [`Client`] and join the given
171    /// server. If the client can't join, it'll keep retrying forever until it
172    /// can.
173    ///
174    /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
175    /// that implements `TryInto<ServerAddress>`.
176    ///
177    /// # Errors
178    ///
179    /// This will error if the given address is invalid or couldn't be resolved
180    /// to a Minecraft server.
181    ///
182    /// [`ServerAddress`]: azalea_protocol::ServerAddress
183    pub async fn start(
184        mut self,
185        account: Account,
186        address: impl TryInto<ServerAddress>,
187    ) -> Result<!, StartError> {
188        self.swarm.accounts = vec![(account, JoinOpts::default())];
189        if self.swarm.states.is_empty() {
190            self.swarm.states = vec![S::default()];
191        }
192        self.swarm.start(address).await
193    }
194
195    /// Do the same as [`Self::start`], but allow passing in custom join
196    /// options.
197    pub async fn start_with_opts(
198        mut self,
199        account: Account,
200        address: impl TryInto<ServerAddress>,
201        opts: JoinOpts,
202    ) -> Result<!, StartError> {
203        self.swarm.accounts = vec![(account, opts.clone())];
204        if self.swarm.states.is_empty() {
205            self.swarm.states = vec![S::default()];
206        }
207        self.swarm.start_with_default_opts(address, opts).await
208    }
209}
210impl Default for ClientBuilder<NoState, ()> {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216/// A marker that can be used in place of a State in [`ClientBuilder`] or
217/// [`SwarmBuilder`]. You probably don't need to use this manually since the
218/// compiler will infer it for you.
219///
220/// [`SwarmBuilder`]: swarm::SwarmBuilder
221#[derive(Component, Clone, Default)]
222pub struct NoState;
223
224/// Optional settings when adding an account to a swarm or client.
225#[derive(Clone, Debug, Default)]
226#[non_exhaustive]
227pub struct JoinOpts {
228    /// The Socks5 proxy that this bot will use.
229    pub proxy: Option<Proxy>,
230    /// Override the server address that this specific bot will send in the
231    /// handshake packet.
232    pub custom_address: Option<ServerAddress>,
233    /// Override the socket address that this specific bot will use to connect
234    /// to the server.
235    pub custom_resolved_address: Option<SocketAddr>,
236}
237
238impl JoinOpts {
239    pub fn new() -> Self {
240        Self::default()
241    }
242
243    pub fn update(&mut self, other: &Self) {
244        if let Some(proxy) = other.proxy.clone() {
245            self.proxy = Some(proxy);
246        }
247        if let Some(custom_address) = other.custom_address.clone() {
248            self.custom_address = Some(custom_address);
249        }
250        if let Some(custom_resolved_address) = other.custom_resolved_address {
251            self.custom_resolved_address = Some(custom_resolved_address);
252        }
253    }
254
255    /// Set the proxy that this bot will use.
256    #[must_use]
257    pub fn proxy(mut self, proxy: Proxy) -> Self {
258        self.proxy = Some(proxy);
259        self
260    }
261    /// Set the custom address that this bot will send in the handshake packet.
262    #[must_use]
263    pub fn custom_address(mut self, custom_address: ServerAddress) -> Self {
264        self.custom_address = Some(custom_address);
265        self
266    }
267    /// Set the custom resolved address that this bot will use to connect to the
268    /// server.
269    #[must_use]
270    pub fn custom_resolved_address(mut self, custom_resolved_address: SocketAddr) -> Self {
271        self.custom_resolved_address = Some(custom_resolved_address);
272        self
273    }
274}