diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs index 98ea4952..3e5f82d8 100644 --- a/azalea-auth/src/auth.rs +++ b/azalea-auth/src/auth.rs @@ -48,7 +48,7 @@ pub enum AuthError { GetXboxLiveAuth(#[from] XboxLiveAuthError), } -/// Authenticate with authenticate with Microsoft. If the data isn't cached, +/// Authenticate with Microsoft. If the data isn't cached, /// they'll be asked to go to log into Microsoft in a web page. /// /// The email is technically only used as a cache key, so it *could* be diff --git a/azalea-buf/src/definitions.rs b/azalea-buf/src/definitions.rs index 77309c46..18581aa0 100644 --- a/azalea-buf/src/definitions.rs +++ b/azalea-buf/src/definitions.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -/// A Vec that isn't prefixed by a VarInt with the size. +/// A `Vec` that isn't prefixed by a VarInt with the size. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnsizedByteArray(pub Vec); diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index fc34d6c4..f63d342e 100644 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -1,13 +1,14 @@ //! Connect to Minecraft servers. -use crate::{client::JoinError, get_mc_dir, Client, Event}; -use azalea_protocol::ServerAddress; -use tokio::sync::mpsc::UnboundedReceiver; +use crate::get_mc_dir; use uuid::Uuid; /// Something that can join Minecraft servers. +/// +/// To join a server using this account, use [`crate::Client::join`]. #[derive(Clone, Debug)] pub struct Account { + /// The Minecraft username of the account. pub username: String, /// The access token for authentication. You can obtain one of these /// manually from azalea-auth. @@ -15,7 +16,11 @@ pub struct Account { /// Only required for online-mode accounts. pub uuid: Option, } + impl Account { + /// An offline account does not authenticate with Microsoft's servers, and + /// as such can only join offline mode servers. This is useful for testing + /// in LAN worlds. pub fn offline(username: &str) -> Self { Self { username: username.to_string(), @@ -24,6 +29,10 @@ impl Account { } } + /// This will create an online-mode account by authenticating with + /// Microsoft's servers. Note that the email given is actually only used as + /// a key for the cache, but it's recommended to use the real email to + /// avoid confusion. pub async fn microsoft(email: &str) -> Result { let minecraft_dir = get_mc_dir::minecraft_dir().unwrap(); let auth_result = azalea_auth::auth( @@ -40,12 +49,4 @@ impl Account { uuid: Some(Uuid::parse_str(&auth_result.profile.id).expect("Invalid UUID")), }) } - - /// Joins the Minecraft server on the given address using this account. - pub async fn join( - &self, - address: &ServerAddress, - ) -> Result<(Client, UnboundedReceiver), JoinError> { - Client::join(self, address).await - } } diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index 6176357f..9e3d58a0 100644 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -11,8 +11,9 @@ use crate::Client; impl Client { /// Sends chat message to the server. This only sends the chat packet and - /// not the command packet. The `chat` function handles checking whether - /// the message is a command and using the proper packet for you. + /// not the command packet. The [`Client::chat`] function handles checking whether + /// the message is a command and using the proper packet for you, so you + /// should use that instead. pub async fn send_chat_packet(&self, message: &str) -> Result<(), std::io::Error> { // TODO: chat signing let signature = sign_message(); @@ -54,6 +55,33 @@ impl Client { self.write_packet(packet).await } + /// Send a message in chat. + /// + /// # Examples + /// + /// ```rust,no_run + /// # use azalea::prelude::*; + /// # use parking_lot::Mutex; + /// # use std::sync::Arc; + /// # #[tokio::main] + /// # async fn main() { + /// # let account = Account::offline("bot"); + /// # azalea::start(azalea::Options { + /// # account, + /// # address: "localhost", + /// # state: Arc::new(Mutex::new(State::default())), + /// # plugins: vec![], + /// # handle, + /// # }) + /// # .await + /// # .unwrap(); + /// # } + /// # pub struct State {} + /// # async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { + /// bot.chat("Hello, world!").await.unwrap(); + /// # Ok(()) + /// # } + /// ``` pub async fn chat(&self, message: &str) -> Result<(), std::io::Error> { if message.chars().next() == Some('/') { self.send_command_packet(&message[1..]).await diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index a59c340b..fc4ff477 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -69,7 +69,7 @@ impl ChatPacket { } } -/// A player that you can control that is currently in a Minecraft server. +/// A player that you control that is currently in a Minecraft server. #[derive(Clone)] pub struct Client { game_profile: GameProfile, @@ -106,6 +106,8 @@ pub enum JoinError { Io(#[from] io::Error), #[error("{0}")] SessionServer(#[from] azalea_auth::sessionserver::SessionServerError), + #[error("The given address could not be parsed into a ServerAddress")] + InvalidAddress, } #[derive(Error, Debug)] @@ -119,12 +121,26 @@ pub enum HandleError { } impl Client { - /// Connect to a Minecraft server with an account. + /// Connect to a Minecraft server. + /// + /// ```rust,no_run + /// use azalea_client::Client; + /// + /// #[tokio::main] + /// async fn main() -> Box { + /// let account = Account::offline("bot"); + /// let client = Client::join(&account, "localhost").await?; + /// client.chat("Hello, world!").await?; + /// client.shutdown().await?; + /// } + /// ``` pub async fn join( account: &Account, - address: &ServerAddress, + address: impl TryInto, ) -> Result<(Self, UnboundedReceiver), JoinError> { - let resolved_address = resolver::resolve_address(address).await?; + let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?; + + let resolved_address = resolver::resolve_address(&address).await?; let mut conn = Connection::new(&resolved_address).await?; diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 1ed30394..61d2d9ee 100755 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -1,4 +1,9 @@ -//! Significantly abstract azalea-protocol so it's actually useable for bots. +//! Significantly abstract [`azalea_protocol`] so it's actually useable for +//! real clients. If you want to make bots, however, you should use the +//! [`azalea`] crate instead. +//! +//! [`azalea_protocol`]: https://crates.io/crates/azalea-protocol +//! [`azalea`]: https://crates.io/crates/azalea mod account; mod chat; diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index 85cf6bdd..93acf36f 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -155,6 +155,8 @@ impl Client { Ok(()) } + /// Makes the bot do one physics tick. Note that this is already handled + /// automatically by the client. pub fn ai_step(&mut self) { self.tick_controls(None); diff --git a/azalea-client/src/ping.rs b/azalea-client/src/ping.rs index c59fb3ac..6817677e 100755 --- a/azalea-client/src/ping.rs +++ b/azalea-client/src/ping.rs @@ -1,4 +1,5 @@ -///! Ping Minecraft servers. +//! Ping Minecraft servers. + use azalea_protocol::{ connect::{Connection, ConnectionError}, packets::{ @@ -25,12 +26,27 @@ pub enum PingError { ReadPacket(#[from] azalea_protocol::read::ReadPacketError), #[error("{0}")] WritePacket(#[from] io::Error), + #[error("The given address could not be parsed into a ServerAddress")] + InvalidAddress, } +/// Ping a Minecraft server. +/// +/// ``` +/// use azalea_client::ping; +/// +/// #[tokio::main] +/// async fn main() { +/// let response = ping::ping_server("play.hypixel.net").await.unwrap(); +/// println!("{}", response.description.to_ansi(None)); +/// } +/// ``` pub async fn ping_server( - address: &ServerAddress, + address: impl TryInto, ) -> Result { - let resolved_address = resolver::resolve_address(address).await?; + let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?; + + let resolved_address = resolver::resolve_address(&address).await?; let mut conn = Connection::new(&resolved_address).await?; diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index 03c56471..c996001d 100644 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs @@ -165,7 +165,7 @@ impl Connection { /// online-mode servers. This must happen when you get a /// `ClientboundLoginPacket::Hello` packet. /// - /// ```no_run + /// ```rust,no_run /// let token = azalea_auth::auth(azalea_auth::AuthOpts { /// ..Default::default() /// }) diff --git a/azalea/README.md b/azalea/README.md index 2ea99de0..d9aa1574 100644 --- a/azalea/README.md +++ b/azalea/README.md @@ -1,3 +1,3 @@ -A framework for creating Minecraft bots. +Azalea is a framework for creating Minecraft bots. -Interally, it's just a wrapper over azalea-client, adding useful functions for making bots. +Internally, it's just a wrapper over azalea-client, adding useful functions for making bots. diff --git a/azalea/examples/craft_dig_straight_down.rs b/azalea/examples/craft_dig_straight_down.rs index 89b11021..48e1fd22 100644 --- a/azalea/examples/craft_dig_straight_down.rs +++ b/azalea/examples/craft_dig_straight_down.rs @@ -11,7 +11,7 @@ struct State { #[tokio::main] async fn main() { let account = Account::offline("bot"); - // or let bot = Account::microsoft("access token").await; + // or let bot = Account::microsoft("email").await; azalea::start(azalea::Options { account, diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs index a5e27d56..a5280d8b 100644 --- a/azalea/examples/echo.rs +++ b/azalea/examples/echo.rs @@ -1,12 +1,13 @@ -use std::sync::Arc; +//! A simple bot that repeats chat messages sent by other players. use azalea::{Account, Client, Event}; use parking_lot::Mutex; +use std::sync::Arc; #[tokio::main] async fn main() { let account = Account::offline("bot"); - // or let account = Account::microsoft("access token").await; + // or let account = Account::microsoft("email").await; azalea::start(azalea::Options { account, @@ -22,7 +23,7 @@ async fn main() { pub struct State {} async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { - match event { + match *event { Event::Chat(m) => { if m.username == bot.username { return Ok(()); // ignore our own messages @@ -33,12 +34,6 @@ async fn handle(bot: Client, event: Arc, state: Arc>) -> any println!(m); bot.reconnect().await.unwrap(); } - Event::HungerUpdate(h) => { - if !h.using_held_item() && h.hunger <= 17 { - bot.hold(azalea::ItemGroup::Food).await?; - bot.use_held_item().await?; - } - } _ => {} } diff --git a/azalea/examples/pvp.rs b/azalea/examples/pvp.rs index 9405cb6f..a2f070f0 100644 --- a/azalea/examples/pvp.rs +++ b/azalea/examples/pvp.rs @@ -1,7 +1,6 @@ -use std::sync::Arc; - use azalea::{pathfinder, Account, Accounts, Client, Event}; use parking_lot::Mutex; +use std::sync::Arc; #[tokio::main] async fn main() { diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 493745cb..19384761 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -1,3 +1,51 @@ +//! 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. +//! +//! # 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: Arc::new(Mutex::new(State::default())), +//! plugins: vec![], +//! handle, +//! }) +//! .await +//! .unwrap(); +//! } +//! +//! pub struct State {} +//! +//! async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { +//! match *event { +//! Event::Chat(m) => { +//! println!(m.message().to_ansi(None)); +//! } +//! _ => {} +//! } +//! +//! Ok(()) +//! } +//! ``` +//! +//! [`azalea_client`]: https://crates.io/crates/azalea-client + mod bot; pub mod prelude; @@ -16,15 +64,27 @@ pub trait Plugin: Send + Sync { pub type HandleFn = fn(Client, Arc, Arc>) -> Fut; +/// The options that are passed to [`azalea::start`]. +/// +/// [`azalea::start`]: fn.start.html pub struct Options where A: TryInto, Fut: Future>, { + /// The address of the server that we're connecting to. This can be a + /// `&str`, [`ServerAddress`], or anything that implements + /// `TryInto`. pub address: A, + /// The account that's going to join the server, pub account: Account, + /// A list of plugins that are going to be used. Plugins are external + /// crates that add extra functionality to Azalea. pub plugins: Vec>, + /// A struct that contains the data that you want your bot to remember + /// across events. pub state: Arc>, + /// The function that's called whenever we get an event. pub handle: HandleFn, } @@ -34,9 +94,10 @@ pub enum Error { InvalidAddress, } -/// Join a Minecraft server. +/// Join a server and start handling events. This function will run forever until +/// it gets disconnected from the server. /// -/// ```no_run +/// ```rust,no_run /// azalea::start(azalea::Options { /// account, /// address: "localhost", @@ -57,7 +118,7 @@ pub async fn start< Err(_) => return Err(Error::InvalidAddress), }; - let (bot, mut rx) = options.account.join(&address).await.unwrap(); + let (bot, mut rx) = Client::join(&options.account, address).await.unwrap(); let state = options.state; let bot_plugin = Arc::new(bot::Plugin::default()); diff --git a/azalea/src/prelude.rs b/azalea/src/prelude.rs index 7b3345b0..0ffb60b8 100644 --- a/azalea/src/prelude.rs +++ b/azalea/src/prelude.rs @@ -1 +1,4 @@ +//! The Azalea prelude. + pub use crate::bot::BotTrait; +pub use azalea_client::{Account, Client, Event};