1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

make handle cleaner

Arc<Event> -> Event, Arc<Mutex<State>> -> State
Items in State now need to have interior mutability (i.e. Arc<Mutex<T>>), but it's a worthwhile tradeoff since it allows the user to customize it for each field
This commit is contained in:
mat 2022-10-23 16:51:49 -05:00
parent a9ff79a105
commit 2eade86cf7
10 changed files with 114 additions and 85 deletions

View file

@ -83,7 +83,7 @@ impl Client {
/// # } /// # }
/// ``` /// ```
pub async fn chat(&self, message: &str) -> Result<(), std::io::Error> { pub async fn chat(&self, message: &str) -> Result<(), std::io::Error> {
if message.chars().next() == Some('/') { if message.starts_with('/') {
self.send_command_packet(&message[1..]).await self.send_command_packet(&message[1..]).await
} else { } else {
self.send_chat_packet(message).await self.send_chat_packet(message).await

View file

@ -3,9 +3,9 @@ use azalea::{Bot, Client, Event};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
#[derive(Default)] #[derive(Default, Clone)]
struct State { struct State {
pub started: bool, pub started: Arc<Mutex<bool>>,
} }
#[tokio::main] #[tokio::main]
@ -16,7 +16,7 @@ async fn main() {
azalea::start(azalea::Options { azalea::start(azalea::Options {
account, account,
address: "localhost", address: "localhost",
state: Arc::new(Mutex::new(State::default())), state: State::default(),
plugins: vec![], plugins: vec![],
handle, handle,
}) })
@ -24,13 +24,13 @@ async fn main() {
.unwrap(); .unwrap();
} }
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) { async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
match event { match event {
Event::Message(m) => { Event::Chat(m) => {
if m.username == bot.player.username { if m.username == bot.player.username {
return; return Ok(());
}; };
if m.message = "go" { if m.content == "go" {
// make sure we only start once // make sure we only start once
let ctx_lock = ctx.lock().unwrap(); let ctx_lock = ctx.lock().unwrap();
if ctx_lock.started { if ctx_lock.started {
@ -74,4 +74,6 @@ async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) {
} }
_ => {} _ => {}
} }
Ok(())
} }

View file

@ -12,7 +12,7 @@ async fn main() {
azalea::start(azalea::Options { azalea::start(azalea::Options {
account, account,
address: "localhost", address: "localhost",
state: Arc::new(Mutex::new(State::default())), state: State::default(),
plugins: vec![], plugins: vec![],
handle, handle,
}) })
@ -20,19 +20,16 @@ async fn main() {
.unwrap(); .unwrap();
} }
#[derive(Default, Clone)]
pub struct State {} pub struct State {}
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> { async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
match *event { match event {
Event::Chat(m) => { Event::Chat(m) => {
if m.username == bot.username { if m.username == bot.username {
return Ok(()); // ignore our own messages return Ok(()); // ignore our own messages
}; };
bot.chat(m.message).await; bot.chat(m.content).await;
}
Event::Kick(m) => {
println!(m);
bot.reconnect().await.unwrap();
} }
_ => {} _ => {}
} }

View file

@ -1,4 +1,4 @@
use azalea::{pathfinder, Account, Accounts, Client, Event}; use azalea::{pathfinder, Account, Accounts, Client, Event, Swarm};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
@ -7,31 +7,31 @@ async fn main() {
let accounts = Accounts::new(); let accounts = Accounts::new();
for i in 0..10 { for i in 0..10 {
accounts.add(Account::offline(format!("bot{}", i))); accounts.add(Account::offline(&format!("bot{}", i)));
} }
azalea::start_group(azalea::GroupOptions { azalea::start_swarm(azalea::SwarmOptions {
accounts, accounts,
address: "localhost", address: "localhost",
group_state: Arc::new(Mutex::new(State::default())), swarm_state: Arc::new(Mutex::new(State::default())),
state: State::default(), state: State::default(),
group_plugins: vec![Arc::new(pathfinder::Plugin::default())], swarm_plugins: vec![Arc::new(pathfinder::Plugin::default())],
plugins: vec![], plugins: vec![],
handle: Box::new(handle), handle: Box::new(handle),
group_handle: Box::new(handle), swarm_handle: Box::new(swarm_handle),
}) })
.await .await
.unwrap(); .unwrap();
} }
#[derive(Default)] #[derive(Default, Clone)]
struct State {} struct State {}
#[derive(Default)] #[derive(Default, Clone)]
struct GroupState {} struct SwarmState {}
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> { async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
match event { match event {
@ -41,26 +41,27 @@ async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> any
Ok(()) Ok(())
} }
async fn group_handle( async fn swarm_handle(
bots: Swarm, swarm: Swarm,
event: Arc<Event>, event: Arc<Event>,
state: Arc<Mutex<GroupState>>, state: Arc<Mutex<SwarmState>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match *event { match *event {
Event::Login => { Event::Login => {
bots.goto(azalea::BlockPos::new(0, 70, 0)).await; swarm.goto(azalea::BlockPos::new(0, 70, 0)).await;
// or bots.goto_goal(pathfinder::Goals::Goto(azalea::BlockPos(0, 70, 0))).await; // or bots.goto_goal(pathfinder::Goals::Goto(azalea::BlockPos(0, 70, 0))).await;
// destroy the blocks in this area and then leave // destroy the blocks in this area and then leave
bots.fill( swarm
azalea::Selection::Range( .fill(
azalea::BlockPos::new(0, 0, 0), azalea::Selection::Range(
azalea::BlockPos::new(16, 255, 16), azalea::BlockPos::new(0, 0, 0),
), azalea::BlockPos::new(16, 255, 16),
azalea::block::Air, ),
) azalea::block::Air,
.await; )
.await;
} }
_ => {} _ => {}
} }

View file

@ -4,17 +4,17 @@ use async_trait::async_trait;
use azalea::{Client, Event}; use azalea::{Client, Event};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Default)] #[derive(Default, Clone)]
pub struct Plugin { pub struct Plugin {
pub state: Arc<Mutex<State>>, pub state: State,
} }
#[derive(Default)] #[derive(Default, Clone)]
pub struct State {} pub struct State {}
#[async_trait] #[async_trait]
impl azalea::Plugin for Plugin { impl azalea::Plugin for Plugin {
async fn handle(self: Arc<Self>, bot: Client, event: Arc<Event>) { async fn handle(self: Box<Self>, event: Event, bot: Client) {
match event { match event {
Event::UpdateHunger => { Event::UpdateHunger => {
if !bot.using_held_item() && bot.food_level() <= 17 { if !bot.using_held_item() && bot.food_level() <= 17 {

View file

@ -17,10 +17,10 @@ async fn main() {
azalea::start(azalea::Options { azalea::start(azalea::Options {
account, account,
address: "localhost", address: "localhost",
state: Arc::new(Mutex::new(State::default())), state: State::default(),
plugins: vec![ plugins: vec![
Arc::new(autoeat::Plugin::default()), Box::new(autoeat::Plugin::default()),
Arc::new(pathfinder::Plugin::default()), Box::new(pathfinder::Plugin::default()),
], ],
handle, handle,
}) })
@ -28,7 +28,7 @@ async fn main() {
.unwrap(); .unwrap();
} }
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> { async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
match event { match event {
Event::Login => { Event::Login => {
goto_farm(bot, state).await?; goto_farm(bot, state).await?;
@ -42,14 +42,14 @@ async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> any
} }
// go to the place where we start farming // go to the place where we start farming
async fn goto_farm(bot: Client, state: Arc<Mutex<State>>) -> anyhow::Result<()> { async fn goto_farm(bot: Client, state: State) -> anyhow::Result<()> {
bot.goto(pathfinder::Goals::Near(5, BlockPos::new(0, 70, 0))) bot.goto(pathfinder::Goals::Near(5, BlockPos::new(0, 70, 0)))
.await?; .await?;
Ok(()) Ok(())
} }
// go to the chest and deposit everything in our inventory. // go to the chest and deposit everything in our inventory.
async fn deposit(bot: &mut Client, state: &mut Arc<Mutex<State>>) -> anyhow::Result<()> { async fn deposit(bot: &mut Client, state: State) -> anyhow::Result<()> {
// first throw away any garbage we might have // first throw away any garbage we might have
bot.toss(|item| item.kind != ItemKind::Potato && item.kind != ItemKind::DiamondHoe); bot.toss(|item| item.kind != ItemKind::Potato && item.kind != ItemKind::DiamondHoe);

View file

@ -7,7 +7,7 @@ async fn main() {
let accounts = Accounts::new(); let accounts = Accounts::new();
for i in 0..10 { for i in 0..10 {
accounts.add(Account::offline(format!("bot{}", i))); accounts.add(Account::offline(&format!("bot{}", i)));
} }
azalea::start_swarm(azalea::SwarmOptions { azalea::start_swarm(azalea::SwarmOptions {
@ -21,24 +21,28 @@ async fn main() {
plugins: vec![], plugins: vec![],
handle: Box::new(handle), handle: Box::new(handle),
swarm_handle: Box::new(handle), swarm_handle: Box::new(swarm_handle),
}) })
.await .await
.unwrap(); .unwrap();
} }
#[derive(Default, Clone)]
struct State {} struct State {}
#[derive(Default, Clone)]
struct SwarmState {} struct SwarmState {}
async fn handle(bots: Client, event: Arc<Event>, state: Arc<Mutex<State>>) { async fn handle(bot: Client, event: Event, state: State) {}
match *event { async fn swarm_handle(swarm: Swarm, event: Event, state: State) {
match event {
Event::Tick => { Event::Tick => {
// choose an arbitrary player within render distance to target // choose an arbitrary player within render distance to target
if let Some(target) = bots if let Some(target) = swarm
.dimension() .dimension
.find_one_entity(|e| e.id == "minecraft:player") .find_one_entity(|e| e.id == "minecraft:player")
{ {
for bot in bots { for bot in swarm {
bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box)); bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box));
// if target.bounding_box.distance(bot.eyes) < bot.reach_distance() { // if target.bounding_box.distance(bot.eyes) < bot.reach_distance() {
if bot.entity.can_reach(target.bounding_box) { if bot.entity.can_reach(target.bounding_box) {

View file

@ -3,14 +3,14 @@ use async_trait::async_trait;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
#[derive(Default)] #[derive(Default, Clone)]
pub struct Plugin { pub struct Plugin {
pub state: Arc<Mutex<State>>, pub state: State,
} }
#[derive(Default)] #[derive(Default, Clone)]
pub struct State { pub struct State {
jumping_once: bool, jumping_once: Arc<Mutex<bool>>,
} }
pub trait BotTrait { pub trait BotTrait {
@ -18,7 +18,7 @@ pub trait BotTrait {
} }
impl BotTrait for azalea_client::Client { impl BotTrait for azalea_client::Client {
/// Try to jump next tick. /// Queue a jump for the next tick.
fn jump(&self) { fn jump(&self) {
let player_lock = self.player.lock(); let player_lock = self.player.lock();
let mut dimension_lock = self.dimension.lock(); let mut dimension_lock = self.dimension.lock();
@ -33,12 +33,11 @@ impl BotTrait for azalea_client::Client {
#[async_trait] #[async_trait]
impl crate::Plugin for Plugin { impl crate::Plugin for Plugin {
async fn handle(self: Arc<Self>, event: Arc<Event>, mut bot: Client) { async fn handle(self: Box<Self>, event: Event, mut bot: Client) {
if let Event::Tick = *event { if let Event::Tick = event {
let mut state = self.state.lock(); if *self.state.jumping_once.lock() {
if state.jumping_once {
if bot.jumping() { if bot.jumping() {
state.jumping_once = false; *self.state.jumping_once.lock() = false;
} else { } else {
bot.set_jumping(true); bot.set_jumping(true);
} }

View file

@ -30,6 +30,7 @@
//! .unwrap(); //! .unwrap();
//! } //! }
//! //!
//! #[derive(Default, Clone)]
//! pub struct State {} //! pub struct State {}
//! //!
//! async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> { //! async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
@ -52,17 +53,35 @@ pub mod prelude;
use async_trait::async_trait; use async_trait::async_trait;
pub use azalea_client::*; pub use azalea_client::*;
use azalea_protocol::ServerAddress; use azalea_protocol::ServerAddress;
use parking_lot::Mutex; use std::future::Future;
use std::{future::Future, sync::Arc};
use thiserror::Error; use thiserror::Error;
/// Plugins can keep their own personal state, listen to events, and add new functions to Client. /// Plugins can keep their own personal state, listen to events, and add new functions to Client.
#[async_trait] #[async_trait]
pub trait Plugin: Send + Sync { pub trait Plugin: Send + Sync + PluginClone + 'static {
async fn handle(self: Arc<Self>, event: Arc<Event>, bot: Client); async fn handle(self: Box<Self>, event: Event, bot: Client);
} }
pub type HandleFn<Fut, S> = fn(Client, Arc<Event>, Arc<Mutex<S>>) -> Fut; /// An internal trait that allows Plugin to be cloned.
#[doc(hidden)]
pub trait PluginClone {
fn clone_box(&self) -> Box<dyn Plugin>;
}
impl<T> PluginClone for T
where
T: 'static + Plugin + Clone,
{
fn clone_box(&self) -> Box<dyn Plugin> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn Plugin> {
fn clone(&self) -> Self {
self.clone_box()
}
}
pub type HandleFn<Fut, S> = fn(Client, Event, S) -> Fut;
/// The options that are passed to [`azalea::start`]. /// The options that are passed to [`azalea::start`].
/// ///
@ -80,10 +99,22 @@ where
pub account: Account, pub account: Account,
/// A list of plugins that are going to be used. Plugins are external /// A list of plugins that are going to be used. Plugins are external
/// crates that add extra functionality to Azalea. /// crates that add extra functionality to Azalea.
pub plugins: Vec<Arc<dyn Plugin>>, pub plugins: Vec<Box<dyn Plugin>>,
/// A struct that contains the data that you want your bot to remember /// A struct that contains the data that you want your bot to remember
/// across events. /// across events.
pub state: Arc<Mutex<S>>, ///
/// # Examples
///
/// ```rust
/// use parking_lot::Mutex;
/// use std::sync::Arc;
///
/// #[derive(Default, Clone)]
/// struct State {
/// farming: Arc<Mutex<bool>>,
/// }
/// ```
pub state: S,
/// The function that's called whenever we get an event. /// The function that's called whenever we get an event.
pub handle: HandleFn<Fut, S>, pub handle: HandleFn<Fut, S>,
} }
@ -107,7 +138,7 @@ pub enum Error {
/// }).await.unwrap(); /// }).await.unwrap();
/// ``` /// ```
pub async fn start< pub async fn start<
S: Send + 'static, S: Send + Sync + Clone + 'static,
A: Send + TryInto<ServerAddress>, A: Send + TryInto<ServerAddress>,
Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
>( >(
@ -121,19 +152,16 @@ pub async fn start<
let (bot, mut rx) = Client::join(&options.account, address).await.unwrap(); let (bot, mut rx) = Client::join(&options.account, address).await.unwrap();
let state = options.state; let state = options.state;
let bot_plugin = Arc::new(bot::Plugin::default()); let bot_plugin = bot::Plugin::default();
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
// we put it into an Arc so it's cheaper to clone
let event = Arc::new(event);
for plugin in &options.plugins { for plugin in &options.plugins {
tokio::spawn(plugin.clone().handle(event.clone(), bot.clone())); let plugin = plugin.clone();
tokio::spawn(plugin.handle(event.clone(), bot.clone()));
} }
tokio::spawn(bot::Plugin::handle( tokio::spawn(bot::Plugin::handle(
bot_plugin.clone(), Box::new(bot_plugin.clone()),
event.clone(), event.clone(),
bot.clone(), bot.clone(),
)); ));

View file

@ -1,9 +1,7 @@
use azalea::prelude::*; use azalea::prelude::*;
use azalea::{Account, Client, Event}; use azalea::{Account, Client, Event};
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Default)] #[derive(Default, Clone)]
struct State {} struct State {}
#[tokio::main] #[tokio::main]
@ -15,7 +13,7 @@ async fn main() -> anyhow::Result<()> {
azalea::start(azalea::Options { azalea::start(azalea::Options {
account, account,
address: "localhost", address: "localhost",
state: Arc::new(Mutex::new(State::default())), state: State::default(),
plugins: vec![], plugins: vec![],
handle, handle,
}) })
@ -25,8 +23,8 @@ async fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
async fn handle(bot: Client, event: Arc<Event>, _state: Arc<Mutex<State>>) -> anyhow::Result<()> { async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
match *event { match event {
Event::Login => { Event::Login => {
bot.chat("Hello world").await?; bot.chat("Hello world").await?;
} }