aboutsummaryrefslogtreecommitdiff
path: root/azalea/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'azalea/src/lib.rs')
-rw-r--r--azalea/src/lib.rs209
1 files changed, 127 insertions, 82 deletions
diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs
index 4ceb5b2e..026a35f5 100644
--- a/azalea/src/lib.rs
+++ b/azalea/src/lib.rs
@@ -1,94 +1,139 @@
-//! Azalea is a framework for creating Minecraft bots.
-//!
-//! Internally, it's just a wrapper over [`azalea_client`], adding useful
-//! functions for making bots. Because of this, lots of the documentation will
-//! refer to `azalea_client`. You can just replace these with `azalea` in your
-//! code, since everything from azalea_client is re-exported in azalea.
-//!
-//! # Installation
-//!
-//! First, install Rust nightly with `rustup install nightly` and `rustup
-//! default nightly`.
-//!
-//! Then, add one of the following lines to your Cargo.toml:
-//!
-//! Latest bleeding-edge version:
-//! `azalea = { git="https://github.com/mat-1/azalea" }`\
-//! Latest "stable" release:
-//! `azalea = "0.5.0"`
-//!
-//! ## Optimization
-//!
-//! For faster compile times, make a `.cargo/config.toml` file in your project
-//! and copy
-//! [this file](https://github.com/mat-1/azalea/blob/main/.cargo/config.toml)
-//! into it.
-//!
-//! For faster performance in debug mode, add
-//! ```toml
-//! [profile.dev]
-//! opt-level = 1
-//! [profile.dev.package."*"]
-//! opt-level = 3
-//! ```
-//! to your Cargo.toml. You may have to install the LLD linker.
-//!
-//! # Examples
-//!
-//! ```rust,no_run
-//! //! A bot that logs chat messages sent in the server to the console.
-//!
-//! use azalea::prelude::*;
-//! use parking_lot::Mutex;
-//! use std::sync::Arc;
-//!
-//! #[tokio::main]
-//! async fn main() {
-//! let account = Account::offline("bot");
-//! // or Account::microsoft("example@example.com").await.unwrap();
-//!
-//! azalea::start(azalea::Options {
-//! account,
-//! address: "localhost",
-//! state: State::default(),
-//! plugins: plugins![],
-//! handle,
-//! })
-//! .await
-//! .unwrap();
-//! }
-//!
-//! #[derive(Default, Clone)]
-//! pub struct State {}
-//!
-//! async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
-//! match event {
-//! Event::Chat(m) => {
-//! println!("{}", m.message().to_ansi());
-//! }
-//! _ => {}
-//! }
-//!
-//! Ok(())
-//! }
-//! ```
-//!
-//! [`azalea_client`]: https://docs.rs/azalea-client
-
-#![feature(trait_upcasting)]
+#![doc = include_str!("../README.md")]
#![feature(async_closure)]
-#![allow(incomplete_features)]
mod bot;
pub mod pathfinder;
pub mod prelude;
-mod start;
mod swarm;
-pub use azalea_block::*;
+pub use azalea_block as blocks;
pub use azalea_client::*;
pub use azalea_core::{BlockPos, Vec3};
-pub use start::{start, Options};
+use azalea_ecs::{
+ app::{App, Plugin},
+ component::Component,
+};
+pub use azalea_protocol as protocol;
+pub use azalea_registry::EntityKind;
+pub use azalea_world::{entity, World};
+use bot::DefaultBotPlugins;
+use ecs::app::PluginGroup;
+use futures::Future;
+use protocol::{
+ resolver::{self, ResolverError},
+ ServerAddress,
+};
pub use swarm::*;
+use thiserror::Error;
+use tokio::sync::mpsc;
pub type HandleFn<Fut, S> = fn(Client, Event, S) -> Fut;
+
+#[derive(Error, Debug)]
+pub enum StartError {
+ #[error("Invalid address")]
+ InvalidAddress,
+ #[error(transparent)]
+ ResolveAddress(#[from] ResolverError),
+ #[error("Join error: {0}")]
+ Join(#[from] azalea_client::JoinError),
+}
+
+pub struct ClientBuilder<S, Fut>
+where
+ S: Default + Send + Sync + Clone + 'static,
+ Fut: Future<Output = Result<(), anyhow::Error>>,
+{
+ app: App,
+ /// The function that's called every time a bot receives an [`Event`].
+ handler: Option<HandleFn<Fut, S>>,
+ state: S,
+}
+impl<S, Fut> ClientBuilder<S, Fut>
+where
+ S: Default + Send + Sync + Clone + Component + 'static,
+ Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+{
+ /// Start building a client that can join the world.
+ #[must_use]
+ pub fn new() -> Self {
+ Self {
+ // we create the app here so plugins can add onto it.
+ // the schedules won't run until [`Self::start`] is called.
+ app: init_ecs_app(),
+
+ handler: None,
+ state: S::default(),
+ }
+ .add_plugins(DefaultBotPlugins)
+ }
+ /// Set the function that's called every time a bot receives an [`Event`].
+ /// This is the way to handle normal per-bot events.
+ ///
+ /// You can only have one client handler, calling this again will replace
+ /// the old client handler function (you can have a client handler and swarm
+ /// handler separately though).
+ #[must_use]
+ pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self {
+ self.handler = Some(handler);
+ self
+ }
+ /// Add a plugin to the client.
+ #[must_use]
+ pub fn add_plugin<T: Plugin>(mut self, plugin: T) -> Self {
+ self.app.add_plugin(plugin);
+ self
+ }
+ /// Add a group of plugins to the client.
+ #[must_use]
+ pub fn add_plugins<T: PluginGroup>(mut self, plugin_group: T) -> Self {
+ self.app.add_plugins(plugin_group);
+ self
+ }
+
+ /// Build this `ClientBuilder` into an actual [`Client`] and join the given
+ /// server.
+ ///
+ /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
+ /// that implements `TryInto<ServerAddress>`.
+ ///
+ /// [`ServerAddress`]: azalea_protocol::ServerAddress
+ pub async fn start(
+ self,
+ account: Account,
+ address: impl TryInto<ServerAddress>,
+ ) -> Result<(), StartError> {
+ let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
+ let resolved_address = resolver::resolve_address(&address).await?;
+
+ // An event that causes the schedule to run. This is only used internally.
+ let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
+ let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone());
+
+ let (bot, mut rx) = Client::start_client(
+ ecs_lock,
+ &account,
+ &address,
+ &resolved_address,
+ run_schedule_sender,
+ )
+ .await?;
+
+ while let Some(event) = rx.recv().await {
+ if let Some(handler) = self.handler {
+ tokio::spawn((handler)(bot.clone(), event.clone(), self.state.clone()));
+ }
+ }
+
+ Ok(())
+ }
+}
+impl<S, Fut> Default for ClientBuilder<S, Fut>
+where
+ S: Default + Send + Sync + Clone + Component + 'static,
+ Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}