mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
make azalea_client keep plugin state
This commit is contained in:
parent
29f442f127
commit
cd01188b8c
18 changed files with 193 additions and 61 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -72,9 +72,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.57"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
|
||||
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -189,6 +189,7 @@ name = "azalea-client"
|
|||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"azalea-auth",
|
||||
"azalea-block",
|
||||
"azalea-chat",
|
||||
|
@ -198,9 +199,11 @@ dependencies = [
|
|||
"azalea-protocol",
|
||||
"azalea-world",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"parking_lot 0.12.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"typemap_rev",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -381,6 +384,7 @@ version = "0.2.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"azalea",
|
||||
"azalea-pathfinder",
|
||||
"env_logger",
|
||||
"parking_lot 0.12.1",
|
||||
"tokio",
|
||||
|
@ -2040,6 +2044,12 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "typemap_rev"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fc45e9608894c9fefd9792d880560280086d73a4d8c8cb7436f27ca98550fb5"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
|
|
|
@ -10,6 +10,7 @@ repository = "https://github.com/mat-1/azalea/tree/main/azalea-client"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.59"
|
||||
async-trait = "0.1.58"
|
||||
azalea-auth = { path = "../azalea-auth", version = "0.3.0" }
|
||||
azalea-block = { path = "../azalea-block", version = "0.3.0" }
|
||||
azalea-chat = { path = "../azalea-chat", version = "0.3.0" }
|
||||
|
@ -19,7 +20,9 @@ azalea-physics = { path = "../azalea-physics", version = "0.3.0" }
|
|||
azalea-protocol = { path = "../azalea-protocol", version = "0.3.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "0.3.0" }
|
||||
log = "0.4.17"
|
||||
nohash-hasher = "0.2.0"
|
||||
parking_lot = "0.12.1"
|
||||
thiserror = "^1.0.34"
|
||||
tokio = { version = "^1.19.2", features = ["sync"] }
|
||||
typemap_rev = "0.2.0"
|
||||
uuid = "^1.1.2"
|
||||
|
|
|
@ -70,7 +70,7 @@ impl Client {
|
|||
/// # account,
|
||||
/// # address: "localhost",
|
||||
/// # state: State::default(),
|
||||
/// # plugins: vec![],
|
||||
/// # plugins: plugins![],
|
||||
/// # handle,
|
||||
/// # })
|
||||
/// # .await
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{movement::WalkDirection, Account, Player};
|
||||
use crate::{movement::WalkDirection, plugins::Plugins, Account, Player};
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_chat::Component;
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
|
@ -90,6 +90,10 @@ pub struct Client {
|
|||
pub dimension: Arc<RwLock<Dimension>>,
|
||||
pub physics_state: Arc<Mutex<PhysicsState>>,
|
||||
pub client_information: Arc<RwLock<ClientInformation>>,
|
||||
/// Plugins are a way for other crates to add custom functionality to the
|
||||
/// client and keep state. If you're not making a plugin and you're using
|
||||
/// the `azalea` crate. you can ignore this field.
|
||||
pub plugins: Arc<Plugins>,
|
||||
tasks: Arc<Mutex<Vec<JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
|
@ -254,8 +258,11 @@ impl Client {
|
|||
player: Arc::new(Mutex::new(Player::default())),
|
||||
dimension: Arc::new(RwLock::new(Dimension::default())),
|
||||
physics_state: Arc::new(Mutex::new(PhysicsState::default())),
|
||||
tasks: Arc::new(Mutex::new(Vec::new())),
|
||||
client_information: Arc::new(RwLock::new(ClientInformation::default())),
|
||||
// The plugins can be modified by the user by replacing the plugins
|
||||
// field right after this. No Mutex so the user doesn't need to .lock().
|
||||
plugins: Arc::new(Plugins::new()),
|
||||
tasks: Arc::new(Mutex::new(Vec::new())),
|
||||
};
|
||||
|
||||
tx.send(Event::Initialize).unwrap();
|
||||
|
|
|
@ -12,11 +12,13 @@ mod get_mc_dir;
|
|||
mod movement;
|
||||
pub mod ping;
|
||||
mod player;
|
||||
mod plugins;
|
||||
|
||||
pub use account::Account;
|
||||
pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError};
|
||||
pub use movement::WalkDirection;
|
||||
pub use player::Player;
|
||||
pub use plugins::{Plugin, Plugins};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
78
azalea-client/src/plugins.rs
Normal file
78
azalea-client/src/plugins.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use crate::{Client, Event};
|
||||
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 Plugins {
|
||||
map: Option<HashMap<TypeId, Box<dyn Plugin>, BuildHasherDefault<NoHashHasher<u64>>>>,
|
||||
}
|
||||
|
||||
impl Plugins {
|
||||
pub fn new() -> Self {
|
||||
Self { map: None }
|
||||
}
|
||||
|
||||
pub fn add<T: Plugin>(&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: Plugin>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.as_ref()
|
||||
.and_then(|map| map.get(&TypeId::of::<T>()))
|
||||
.and_then(|boxed| (&*boxed as &(dyn Any + 'static)).downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Plugins {
|
||||
type Item = Box<dyn Plugin>;
|
||||
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 Plugin: Send + Sync + PluginClone + 'static {
|
||||
async fn handle(self: Box<Self>, event: Event, bot: Client);
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
|
@ -114,3 +114,27 @@ impl Node {
|
|||
entity.yya == 0. && BlockPos::from(entity.pos()) == self.pos
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockPosGoal {
|
||||
pub pos: BlockPos,
|
||||
}
|
||||
impl Goal for BlockPosGoal {
|
||||
fn heuristic(&self, n: &Node) -> f32 {
|
||||
let dx = (self.pos.x - n.pos.x) as f32;
|
||||
let dy = (self.pos.y - n.pos.y) as f32;
|
||||
let dz = (self.pos.z - n.pos.z) as f32;
|
||||
dx * dx + dy * dy + dz * dz
|
||||
}
|
||||
fn success(&self, n: &Node) -> bool {
|
||||
n.pos == self.pos
|
||||
}
|
||||
fn goal_node(&self) -> Node {
|
||||
Node { pos: self.pos }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockPos> for BlockPosGoal {
|
||||
fn from(pos: BlockPos) -> Self {
|
||||
Self { pos }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,10 @@ impl Chunk {
|
|||
}
|
||||
|
||||
pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
|
||||
if pos.y < min_y {
|
||||
// y position is out of bounds
|
||||
return None;
|
||||
}
|
||||
let section_index = self.section_index(pos.y, min_y) as usize;
|
||||
if section_index >= self.sections.len() {
|
||||
// y position is out of bounds
|
||||
|
|
|
@ -17,7 +17,7 @@ async fn main() {
|
|||
account,
|
||||
address: "localhost",
|
||||
state: State::default(),
|
||||
plugins: vec![],
|
||||
plugins: plugins![],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -11,7 +11,7 @@ async fn main() {
|
|||
account,
|
||||
address: "localhost",
|
||||
state: State::default(),
|
||||
plugins: vec![],
|
||||
plugins: plugins![],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea::{pathfinder, Account, Accounts, Client, Event, Swarm};
|
||||
use azalea::{Account, Accounts, Client, Event, Swarm};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -17,8 +17,8 @@ async fn main() {
|
|||
swarm_state: State::default(),
|
||||
state: State::default(),
|
||||
|
||||
swarm_plugins: vec![Arc::new(pathfinder::Plugin::default())],
|
||||
plugins: vec![],
|
||||
swarm_plugins: plugins![azalea_pathfinder::Plugin::default()],
|
||||
plugins: plugins![],
|
||||
|
||||
handle: Box::new(handle),
|
||||
swarm_handle: Box::new(swarm_handle),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mod autoeat;
|
||||
|
||||
use azalea::prelude::*;
|
||||
use azalea::{pathfinder, BlockPos, ItemKind, Vec3};
|
||||
|
||||
|
@ -16,10 +15,7 @@ async fn main() {
|
|||
account,
|
||||
address: "localhost",
|
||||
state: State::default(),
|
||||
plugins: vec![
|
||||
Box::new(autoeat::Plugin::default()),
|
||||
Box::new(pathfinder::Plugin::default()),
|
||||
],
|
||||
plugins: plugins![autoeat::Plugin::default(), pathfinder::Plugin::default(),],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -15,8 +15,8 @@ async fn main() {
|
|||
swarm_state: State::default(),
|
||||
state: State::default(),
|
||||
|
||||
swarm_plugins: vec![Box::new(pathfinder::Plugin::default())],
|
||||
plugins: vec![],
|
||||
swarm_plugins: plugins![pathfinder::Plugin::default()],
|
||||
plugins: plugins![],
|
||||
|
||||
handle: Box::new(handle),
|
||||
swarm_handle: Box::new(swarm_handle),
|
||||
|
|
|
@ -29,6 +29,8 @@ impl BotTrait for azalea_client::Client {
|
|||
.entity_mut(&mut dimension_lock)
|
||||
.expect("Player must exist");
|
||||
|
||||
let state = self.plugins.get::<Plugin>().unwrap().state.clone();
|
||||
*state.jumping_once.lock() = true;
|
||||
player_entity.jumping = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
//! 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/Cargo.toml" }`
|
||||
//! Latest "stable" release:
|
||||
//! `azalea = "0.3"`
|
||||
//!
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
|
@ -34,7 +34,7 @@
|
|||
//! account,
|
||||
//! address: "localhost",
|
||||
//! state: State::default(),
|
||||
//! plugins: vec![],
|
||||
//! plugins: plugins![],
|
||||
//! handle,
|
||||
//! })
|
||||
//! .await
|
||||
|
@ -61,37 +61,12 @@
|
|||
mod bot;
|
||||
pub mod prelude;
|
||||
|
||||
use async_trait::async_trait;
|
||||
pub use azalea_client::*;
|
||||
pub use azalea_core::{BlockPos, Vec3};
|
||||
use azalea_protocol::ServerAddress;
|
||||
use std::future::Future;
|
||||
use std::{future::Future, sync::Arc};
|
||||
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 + PluginClone + 'static {
|
||||
async fn handle(self: Box<Self>, event: Event, bot: Client);
|
||||
}
|
||||
|
||||
/// 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`].
|
||||
|
@ -110,9 +85,14 @@ where
|
|||
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<Box<dyn Plugin>>,
|
||||
/// The plugins that are going to be used. Plugins are external crates that
|
||||
/// add extra functionality to Azalea. You should use the [`plugins`] macro
|
||||
/// for this field.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// plugins![azalea_pathfinder::Plugin::default()]
|
||||
/// ```
|
||||
pub plugins: Plugins,
|
||||
/// A struct that contains the data that you want your bot to remember
|
||||
/// across events.
|
||||
///
|
||||
|
@ -160,7 +140,7 @@ pub enum Error {
|
|||
/// account,
|
||||
/// address: "localhost",
|
||||
/// state: State::default(),
|
||||
/// plugins: vec![Box::new(autoeat::Plugin::default())],
|
||||
/// plugins: plugins![azalea_pathfinder::Plugin::default()],
|
||||
/// handle,
|
||||
/// }).await;
|
||||
/// ```
|
||||
|
@ -176,14 +156,16 @@ pub async fn start<
|
|||
Err(_) => return Err(Error::InvalidAddress),
|
||||
};
|
||||
|
||||
let (bot, mut rx) = Client::join(&options.account, address).await?;
|
||||
let (mut bot, mut rx) = Client::join(&options.account, address).await?;
|
||||
|
||||
bot.plugins = Arc::new(options.plugins);
|
||||
|
||||
let state = options.state;
|
||||
let bot_plugin = bot::Plugin::default();
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
for plugin in &options.plugins {
|
||||
let plugin = plugin.clone();
|
||||
let cloned_plugins = (*bot.plugins).clone();
|
||||
for plugin in cloned_plugins.into_iter() {
|
||||
tokio::spawn(plugin.handle(event.clone(), bot.clone()));
|
||||
}
|
||||
|
||||
|
@ -197,3 +179,23 @@ pub async fn start<
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// 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_client::Plugins::new();
|
||||
$(
|
||||
plugins.add($plugin);
|
||||
)*
|
||||
plugins
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! The Azalea prelude. Things that are necessary for a bare-bones bot are re-exported here.
|
||||
|
||||
pub use crate::bot::BotTrait;
|
||||
pub use crate::plugins;
|
||||
pub use azalea_client::{Account, Client, Event};
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "bot"
|
||||
version = "0.2.0"
|
||||
publish = false
|
||||
release = false
|
||||
version = "0.2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.65"
|
||||
azalea = { path = "../azalea" }
|
||||
azalea = {path = "../azalea"}
|
||||
azalea-pathfinder = {path = "../azalea-pathfinder"}
|
||||
env_logger = "0.9.1"
|
||||
parking_lot = "^0.12.1"
|
||||
tokio = "1.19.2"
|
||||
uuid = "1.1.2"
|
||||
parking_lot = "^0.12.1"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use azalea::prelude::*;
|
||||
use azalea::{prelude::*, BlockPos};
|
||||
use azalea::{Account, Client, Event};
|
||||
use azalea_pathfinder::{BlockPosGoal, Trait};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct State {}
|
||||
|
@ -14,7 +15,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
account,
|
||||
address: "localhost",
|
||||
state: State::default(),
|
||||
plugins: vec![],
|
||||
plugins: vec![Box::new(azalea_pathfinder::Plugin::default())],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
|
@ -27,12 +28,13 @@ async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()>
|
|||
match event {
|
||||
Event::Login => {
|
||||
bot.chat("Hello world").await?;
|
||||
bot.goto(BlockPosGoal::from(BlockPos::new(0, -60, 12)));
|
||||
}
|
||||
Event::Initialize => {
|
||||
println!("initialized");
|
||||
}
|
||||
Event::Tick => {
|
||||
bot.jump();
|
||||
// bot.jump();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue