1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00
This commit is contained in:
mat 2022-11-13 13:46:07 -06:00
parent 04c8d8db9f
commit 9b07178a06
9 changed files with 215 additions and 49 deletions

1
Cargo.lock generated
View file

@ -121,6 +121,7 @@ dependencies = [
"azalea-world",
"env_logger",
"futures",
"nohash-hasher",
"num-traits",
"parking_lot",
"priority-queue",

View file

@ -10,7 +10,7 @@ version = "0.3.0"
[dependencies]
anyhow = "^1.0.65"
async-trait = "^0.1.57"
async-trait = "0.1.58"
azalea-block = {version = "0.3.0", path = "../azalea-block"}
azalea-client = {version = "0.3.0", path = "../azalea-client"}
azalea-core = {version = "0.3.0", path = "../azalea-core"}
@ -18,6 +18,7 @@ azalea-physics = {version = "0.3.0", path = "../azalea-physics"}
azalea-protocol = {version = "0.3.0", path = "../azalea-protocol"}
azalea-world = {version = "0.3.0", path = "../azalea-world"}
futures = "0.3.25"
nohash-hasher = "0.2.0"
num-traits = "0.2.15"
parking_lot = {version = "^0.12.1", features = ["deadlock_detection"]}
priority-queue = "1.3.0"

View file

@ -10,6 +10,7 @@ async fn main() {
for i in 0..10 {
accounts.push(Account::offline(&format!("bot{}", i)));
states.push(Arc::new(Mutex::new(State::default())));
}
azalea::start_swarm(azalea::SwarmOptions {

View file

@ -75,6 +75,9 @@
//!
//! [`azalea_client`]: https://crates.io/crates/azalea-client
#![feature(trait_upcasting)]
#![allow(incomplete_features)]
mod bot;
pub mod pathfinder;
pub mod prelude;
@ -87,22 +90,3 @@ pub use start::{start, Options};
pub use swarm::*;
pub type HandleFn<Fut, S> = fn(Client, Event, S) -> Fut;
/// A helper macro that generates a [`Plugins`] struct from a list of objects
/// that implement [`Plugin`].
///
/// ```rust,no_run
/// plugins![azalea_pathfinder::Plugin::default()];
/// ```
#[macro_export]
macro_rules! plugins {
($($plugin:expr),*) => {
{
let mut plugins = azalea::Plugins::new();
$(
plugins.add($plugin);
)*
plugins
}
};
}

View file

@ -2,5 +2,5 @@
pub use crate::bot::BotTrait;
pub use crate::pathfinder::Trait;
pub use crate::plugins;
pub use crate::{plugins, swarm_plugins};
pub use azalea_client::{Account, Client, Event};

View file

@ -4,6 +4,25 @@ use azalea_protocol::ServerAddress;
use std::{future::Future, sync::Arc};
use thiserror::Error;
/// A helper macro that generates a [`Plugins`] struct from a list of objects
/// that implement [`Plugin`].
///
/// ```rust,no_run
/// plugins![azalea_pathfinder::Plugin::default()];
/// ```
#[macro_export]
macro_rules! plugins {
($($plugin:expr),*) => {
{
let mut plugins = azalea::Plugins::new();
$(
plugins.add($plugin);
)*
plugins
}
};
}
/// The options that are passed to [`azalea::start`].
///
/// [`azalea::start`]: crate::start

View file

@ -1,4 +1,8 @@
mod plugins;
pub use self::plugins::*;
use crate::{bot, HandleFn};
use async_trait::async_trait;
use azalea_client::{Account, Client, Event, JoinError, Plugin, Plugins};
use azalea_protocol::{
resolver::{self, ResolverError},
@ -9,10 +13,29 @@ use futures::{
FutureExt,
};
use parking_lot::Mutex;
use std::{future::Future, sync::Arc};
use std::{any::Any, future::Future, sync::Arc};
use thiserror::Error;
use tokio::sync::mpsc::UnboundedReceiver;
/// A helper macro that generates a [`Plugins`] struct from a list of objects
/// that implement [`Plugin`].
///
/// ```rust,no_run
/// plugins![azalea_pathfinder::Plugin::default()];
/// ```
#[macro_export]
macro_rules! swarm_plugins {
($($plugin:expr),*) => {
{
let mut plugins = azalea::SwarmPlugins::new();
$(
plugins.add($plugin);
)*
plugins
}
};
}
/// A swarm is a way to conveniently control many bots at once, while also
/// being able to control bots at an individual level when desired.
#[derive(Clone)]
@ -28,13 +51,16 @@ pub enum SwarmEvent {
Login,
}
pub type SwarmHandleFn<Fut, S> = fn(Swarm, SwarmEvent, S) -> Fut;
/// The options that are passed to [`azalea::start_swarm`].
///
/// [`azalea::start`]: crate::start_swarm
pub struct SwarmOptions<S, SS, A, Fut>
pub struct SwarmOptions<S, SS, A, Fut, SwarmFut>
where
A: TryInto<ServerAddress>,
Fut: Future<Output = Result<(), anyhow::Error>>,
SwarmFut: Future<Output = Result<(), anyhow::Error>>,
{
/// The address of the server that we're connecting to. This can be a
/// `&str`, [`ServerAddress`], or anything that implements
@ -45,13 +71,13 @@ where
/// The accounts that are going to join the server.
pub accounts: Vec<Account>,
pub plugins: Plugins,
pub swarm_plugins: Plugins,
pub swarm_plugins: SwarmPlugins,
/// The individual bot states. This must be the same length as `accounts`,
/// since each bot gets one state.
pub states: Vec<S>,
pub swarm_state: SS,
pub handle: HandleFn<Fut, S>,
pub swarm_handle: HandleFn<Fut, S>,
pub swarm_handle: SwarmHandleFn<SwarmFut, SS>,
}
#[derive(Error, Debug)]
@ -70,8 +96,9 @@ pub async fn start_swarm<
SS: Send + Sync + Clone + 'static,
A: Send + TryInto<ServerAddress>,
Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
>(
options: SwarmOptions<S, SS, A, Fut>,
options: SwarmOptions<S, SS, A, Fut, SwarmFut>,
) -> Result<(), SwarmStartError> {
assert_eq!(
options.accounts.len(),
@ -116,6 +143,21 @@ pub async fn start_swarm<
let states = options.states;
let swarm_state = options.swarm_state;
let mut internal_state = InternalSwarmState::default();
// Send an event to the swarm_handle function.
let cloned_swarm = swarm.clone();
let fire_swarm_event = move |event: SwarmEvent| {
let cloned_swarm_plugins = options.swarm_plugins.clone();
for plugin in cloned_swarm_plugins.into_iter() {
tokio::spawn(plugin.handle(event.clone(), cloned_swarm.clone()));
}
tokio::spawn((options.swarm_handle)(
cloned_swarm.clone(),
event,
swarm_state.clone(),
));
};
// bot events
while let (Some(event), bot_index) = swarm.bot_recv().await {
@ -132,6 +174,18 @@ pub async fn start_swarm<
event.clone(),
bot.clone(),
));
// swarm event handling
match &event {
Event::Login => {
internal_state.clients_joined += 1;
if internal_state.clients_joined == swarm.bots.lock().len() {
fire_swarm_event(SwarmEvent::Login);
}
}
_ => {}
}
tokio::spawn((options.handle)(bot, event, bot_state));
}
@ -151,3 +205,9 @@ impl Swarm {
(event, index)
}
}
#[derive(Default)]
struct InternalSwarmState {
/// The number of clients connected to the server
pub clients_joined: usize,
}

View file

@ -0,0 +1,78 @@
use crate::{Client, Event, Swarm, SwarmEvent};
use async_trait::async_trait;
use nohash_hasher::NoHashHasher;
use std::{
any::{Any, TypeId},
collections::HashMap,
hash::BuildHasherDefault,
};
// kind of based on https://docs.rs/http/latest/src/http/extensions.rs.html
/// A map of plugin ids to Plugin trait objects. The client stores this so we
/// can keep the state for our plugins.
///
/// If you're using azalea, you should generate this from the `plugins!` macro.
#[derive(Clone)]
pub struct SwarmPlugins {
map: Option<HashMap<TypeId, Box<dyn SwarmPlugin>, BuildHasherDefault<NoHashHasher<u64>>>>,
}
impl SwarmPlugins {
pub fn new() -> Self {
Self { map: None }
}
pub fn add<T: SwarmPlugin>(&mut self, plugin: T) {
if self.map.is_none() {
self.map = Some(HashMap::with_hasher(BuildHasherDefault::default()));
}
self.map
.as_mut()
.unwrap()
.insert(TypeId::of::<T>(), Box::new(plugin));
}
pub fn get<T: SwarmPlugin>(&self) -> Option<&T> {
self.map
.as_ref()
.and_then(|map| map.get(&TypeId::of::<T>()))
.and_then(|boxed| (boxed.as_ref() as &dyn Any).downcast_ref::<T>())
}
}
impl IntoIterator for SwarmPlugins {
type Item = Box<dyn SwarmPlugin>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.map
.map(|map| map.into_iter().map(|(_, v)| v).collect::<Vec<_>>())
.unwrap_or_default()
.into_iter()
}
}
/// Plugins can keep their own personal state, listen to events, and add new functions to Client.
#[async_trait]
pub trait SwarmPlugin: Send + Sync + SwarmPluginClone + Any + 'static {
async fn handle(self: Box<Self>, event: SwarmEvent, swarm: Swarm);
}
/// An internal trait that allows Plugin to be cloned.
#[doc(hidden)]
pub trait SwarmPluginClone {
fn clone_box(&self) -> Box<dyn SwarmPlugin>;
}
impl<T> SwarmPluginClone for T
where
T: 'static + SwarmPlugin + Clone,
{
fn clone_box(&self) -> Box<dyn SwarmPlugin> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn SwarmPlugin> {
fn clone(&self) -> Self {
self.clone_box()
}
}

View file

@ -1,10 +1,13 @@
use azalea::pathfinder::BlockPosGoal;
use azalea::{prelude::*, BlockPos};
use azalea::{prelude::*, BlockPos, Swarm, SwarmEvent};
use azalea::{Account, Client, Event};
#[derive(Default, Clone)]
struct State {}
#[derive(Default, Clone)]
struct SwarmState {}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
@ -32,44 +35,55 @@ async fn main() -> anyhow::Result<()> {
}
}
});
} // only for #[cfg]
}
// let account = Account::microsoft("example@example.com").await?;
let account = Account::offline("bot");
let mut accounts = Vec::new();
let mut states = Vec::new();
for i in 0..5 {
accounts.push(Account::offline(&format!("bot{}", i)));
states.push(State::default());
}
loop {
let e = azalea::start(azalea::Options {
account: account.clone(),
let e = azalea::start_swarm(azalea::SwarmOptions {
accounts: accounts.clone(),
address: "localhost",
state: State::default(),
states: states.clone(),
swarm_state: SwarmState::default(),
plugins: plugins![],
swarm_plugins: swarm_plugins![],
handle,
swarm_handle,
})
.await;
println!("{:?}", e);
}
}
async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
match event {
Event::Login => {
// bot.chat("Hello world").await?;
bot.chat("Hello world").await?;
}
Event::Chat(m) => {
println!("{}", m.message().to_ansi(None));
if m.message().to_string() == "<py5> goto" {
let target_pos_vec3 = bot
.dimension
.read()
.entity_by_uuid(&uuid::uuid!("6536bfed869548fd83a1ecd24cf2a0fd"))
.unwrap()
.pos()
.clone();
let target_pos: BlockPos = (&target_pos_vec3).into();
// bot.look_at(&target_pos_vec3);
bot.goto(BlockPosGoal::from(target_pos));
// bot.walk(WalkDirection::Forward);
}
// println!("{}", m.message().to_ansi(None));
// if m.message().to_string() == "<py5> goto" {
// let target_pos_vec3 = bot
// .dimension
// .read()
// .entity_by_uuid(&uuid::uuid!("6536bfed869548fd83a1ecd24cf2a0fd"))
// .unwrap()
// .pos()
// .clone();
// let target_pos: BlockPos = (&target_pos_vec3).into();
// // bot.look_at(&target_pos_vec3);
// bot.goto(BlockPosGoal::from(target_pos));
// // bot.walk(WalkDirection::Forward);
// }
}
Event::Initialize => {
println!("initialized");
@ -82,3 +96,11 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
Ok(())
}
async fn swarm_handle(
mut _swarm: Swarm,
_event: SwarmEvent,
_state: SwarmState,
) -> anyhow::Result<()> {
Ok(())
}