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> {
if message.chars().next() == Some('/') {
if message.starts_with('/') {
self.send_command_packet(&message[1..]).await
} else {
self.send_chat_packet(message).await

View file

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

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 std::sync::Arc;
@ -7,31 +7,31 @@ async fn main() {
let accounts = Accounts::new();
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,
address: "localhost",
group_state: Arc::new(Mutex::new(State::default())),
swarm_state: Arc::new(Mutex::new(State::default())),
state: State::default(),
group_plugins: vec![Arc::new(pathfinder::Plugin::default())],
swarm_plugins: vec![Arc::new(pathfinder::Plugin::default())],
plugins: vec![],
handle: Box::new(handle),
group_handle: Box::new(handle),
swarm_handle: Box::new(swarm_handle),
})
.await
.unwrap();
}
#[derive(Default)]
#[derive(Default, Clone)]
struct State {}
#[derive(Default)]
struct GroupState {}
#[derive(Default, Clone)]
struct SwarmState {}
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
match event {
@ -41,26 +41,27 @@ async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> any
Ok(())
}
async fn group_handle(
bots: Swarm,
async fn swarm_handle(
swarm: Swarm,
event: Arc<Event>,
state: Arc<Mutex<GroupState>>,
state: Arc<Mutex<SwarmState>>,
) -> anyhow::Result<()> {
match *event {
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;
// destroy the blocks in this area and then leave
bots.fill(
azalea::Selection::Range(
azalea::BlockPos::new(0, 0, 0),
azalea::BlockPos::new(16, 255, 16),
),
azalea::block::Air,
)
.await;
swarm
.fill(
azalea::Selection::Range(
azalea::BlockPos::new(0, 0, 0),
azalea::BlockPos::new(16, 255, 16),
),
azalea::block::Air,
)
.await;
}
_ => {}
}

View file

@ -4,17 +4,17 @@ use async_trait::async_trait;
use azalea::{Client, Event};
use std::sync::{Arc, Mutex};
#[derive(Default)]
#[derive(Default, Clone)]
pub struct Plugin {
pub state: Arc<Mutex<State>>,
pub state: State,
}
#[derive(Default)]
#[derive(Default, Clone)]
pub struct State {}
#[async_trait]
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 {
Event::UpdateHunger => {
if !bot.using_held_item() && bot.food_level() <= 17 {

View file

@ -17,10 +17,10 @@ async fn main() {
azalea::start(azalea::Options {
account,
address: "localhost",
state: Arc::new(Mutex::new(State::default())),
state: State::default(),
plugins: vec![
Arc::new(autoeat::Plugin::default()),
Arc::new(pathfinder::Plugin::default()),
Box::new(autoeat::Plugin::default()),
Box::new(pathfinder::Plugin::default()),
],
handle,
})
@ -28,7 +28,7 @@ async fn main() {
.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 {
Event::Login => {
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
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)))
.await?;
Ok(())
}
// 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
bot.toss(|item| item.kind != ItemKind::Potato && item.kind != ItemKind::DiamondHoe);

View file

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

View file

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

View file

@ -30,6 +30,7 @@
//! .unwrap();
//! }
//!
//! #[derive(Default, Clone)]
//! pub struct State {}
//!
//! 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;
pub use azalea_client::*;
use azalea_protocol::ServerAddress;
use parking_lot::Mutex;
use std::{future::Future, sync::Arc};
use std::future::Future;
use thiserror::Error;
/// Plugins can keep their own personal state, listen to events, and add new functions to Client.
#[async_trait]
pub trait Plugin: Send + Sync {
async fn handle(self: Arc<Self>, event: Arc<Event>, bot: Client);
pub trait Plugin: Send + Sync + PluginClone + 'static {
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`].
///
@ -80,10 +99,22 @@ where
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<Arc<dyn Plugin>>,
pub plugins: Vec<Box<dyn Plugin>>,
/// A struct that contains the data that you want your bot to remember
/// 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.
pub handle: HandleFn<Fut, S>,
}
@ -107,7 +138,7 @@ pub enum Error {
/// }).await.unwrap();
/// ```
pub async fn start<
S: Send + 'static,
S: Send + Sync + Clone + 'static,
A: Send + TryInto<ServerAddress>,
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 state = options.state;
let bot_plugin = Arc::new(bot::Plugin::default());
let bot_plugin = bot::Plugin::default();
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 {
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(
bot_plugin.clone(),
Box::new(bot_plugin.clone()),
event.clone(),
bot.clone(),
));

View file

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