diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2022-11-12 23:54:05 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-12 23:54:05 -0600 |
| commit | 6eee543a3367d38a6f0e9bffb457a2bd76a8f9cc (patch) | |
| tree | a5e493ccd7ec24293b8d866242c3836146517122 /azalea-client/src | |
| parent | fa57d03627aa20b1df44caed7cb025b6db1d9b53 (diff) | |
| download | azalea-drasl-6eee543a3367d38a6f0e9bffb457a2bd76a8f9cc.tar.xz | |
Pathfinder (#25)
Pathfinding is very much not done, but it works enough and I want to get this merged.
TODO: fast replanning, goals that aren't a single node, falling moves (it should be able to play the dropper), parkour moves
Diffstat (limited to 'azalea-client/src')
| -rwxr-xr-x[-rw-r--r--] | azalea-client/src/account.rs | 0 | ||||
| -rwxr-xr-x[-rw-r--r--] | azalea-client/src/chat.rs | 2 | ||||
| -rw-r--r-- | azalea-client/src/client.rs | 97 | ||||
| -rwxr-xr-x[-rw-r--r--] | azalea-client/src/get_mc_dir.rs | 0 | ||||
| -rwxr-xr-x | azalea-client/src/lib.rs | 7 | ||||
| -rwxr-xr-x[-rw-r--r--] | azalea-client/src/movement.rs | 192 | ||||
| -rwxr-xr-x[-rw-r--r--] | azalea-client/src/player.rs | 6 | ||||
| -rw-r--r-- | azalea-client/src/plugins.rs | 78 |
8 files changed, 302 insertions, 80 deletions
diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 42bfe6fc..42bfe6fc 100644..100755 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index b771a743..5eadc0c3 100644..100755 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -70,7 +70,7 @@ impl Client { /// # account, /// # address: "localhost", /// # state: State::default(), - /// # plugins: vec![], + /// # plugins: plugins![], /// # handle, /// # }) /// # .await diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 3d6c8e05..282de48f 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,4 +1,4 @@ -use crate::{movement::MoveDirection, 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}; @@ -28,11 +28,11 @@ use azalea_protocol::{ resolver, ServerAddress, }; use azalea_world::{ - entity::{metadata, EntityData, EntityMetadata, EntityMut, EntityRef}, + entity::{metadata, Entity, EntityData, EntityMetadata}, Dimension, }; use log::{debug, error, info, warn}; -use parking_lot::{Mutex, RwLock}; +use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ fmt::Debug, io::{self, Cursor}, @@ -86,10 +86,14 @@ pub struct Client { game_profile: GameProfile, pub read_conn: Arc<tokio::sync::Mutex<ReadConnection<ClientboundGamePacket>>>, pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>, - pub player: Arc<Mutex<Player>>, - pub dimension: Arc<Mutex<Dimension>>, + pub player: Arc<RwLock<Player>>, + 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<()>>>>, } @@ -97,8 +101,12 @@ pub struct Client { pub struct PhysicsState { /// Minecraft only sends a movement packet either after 20 ticks or if the player moved enough. This is that tick counter. pub position_remainder: u32, + pub was_sprinting: bool, + // Whether we're going to try to start sprinting this tick. Equivalent to + // holding down ctrl for a tick. + pub trying_to_sprint: bool, - pub move_direction: MoveDirection, + pub move_direction: WalkDirection, pub forward_impulse: f32, pub left_impulse: f32, } @@ -253,11 +261,14 @@ impl Client { game_profile, read_conn, write_conn, - player: Arc::new(Mutex::new(Player::default())), - dimension: Arc::new(Mutex::new(Dimension::default())), + player: Arc::new(RwLock::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(); @@ -403,7 +414,7 @@ impl Client { .as_int() .expect("min_y tag is not an int"); - let mut dimension_lock = client.dimension.lock(); + let mut dimension_lock = client.dimension.write(); // the 16 here is our render distance // i'll make this an actual setting later *dimension_lock = Dimension::new(16, height, min_y); @@ -415,7 +426,7 @@ impl Client { ); dimension_lock.add_entity(p.player_id, entity); - let mut player_lock = client.player.lock(); + let mut player_lock = client.player.write(); player_lock.set_entity_id(p.player_id); } @@ -482,11 +493,11 @@ impl Client { let (new_pos, y_rot, x_rot) = { let player_entity_id = { - let player_lock = client.player.lock(); + let player_lock = client.player.write(); player_lock.entity_id }; - let mut dimension_lock = client.dimension.lock(); + let mut dimension_lock = client.dimension.write(); let mut player_entity = dimension_lock .entity_mut(player_entity_id) @@ -574,7 +585,7 @@ impl Client { debug!("Got chunk cache center packet {:?}", p); client .dimension - .lock() + .write() .update_view_center(&ChunkPos::new(p.x, p.z)); } ClientboundGamePacket::LevelChunkWithLight(p) => { @@ -584,7 +595,7 @@ impl Client { // debug("chunk {:?}") if let Err(e) = client .dimension - .lock() + .write() .replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data)) { error!("Couldn't set chunk data: {}", e); @@ -596,11 +607,11 @@ impl Client { ClientboundGamePacket::AddEntity(p) => { debug!("Got add entity packet {:?}", p); let entity = EntityData::from(p); - client.dimension.lock().add_entity(p.id, entity); + client.dimension.write().add_entity(p.id, entity); } ClientboundGamePacket::SetEntityData(p) => { debug!("Got set entity data packet {:?}", p); - let mut dimension = client.dimension.lock(); + let mut dimension = client.dimension.write(); if let Some(mut entity) = dimension.entity_mut(p.id) { entity.apply_metadata(&p.packed_items.0); } else { @@ -619,7 +630,7 @@ impl Client { ClientboundGamePacket::AddPlayer(p) => { debug!("Got add player packet {:?}", p); let entity = EntityData::from(p); - client.dimension.lock().add_entity(p.id, entity); + client.dimension.write().add_entity(p.id, entity); } ClientboundGamePacket::InitializeBorder(p) => { debug!("Got initialize border packet {:?}", p); @@ -640,7 +651,7 @@ impl Client { debug!("Got set experience packet {:?}", p); } ClientboundGamePacket::TeleportEntity(p) => { - let mut dimension_lock = client.dimension.lock(); + let mut dimension_lock = client.dimension.write(); dimension_lock .set_entity_pos( @@ -660,14 +671,14 @@ impl Client { // debug!("Got rotate head packet {:?}", p); } ClientboundGamePacket::MoveEntityPos(p) => { - let mut dimension_lock = client.dimension.lock(); + let mut dimension_lock = client.dimension.write(); dimension_lock .move_entity_with_delta(p.entity_id, &p.delta) .map_err(|e| HandleError::Other(e.into()))?; } ClientboundGamePacket::MoveEntityPosRot(p) => { - let mut dimension_lock = client.dimension.lock(); + let mut dimension_lock = client.dimension.write(); dimension_lock .move_entity_with_delta(p.entity_id, &p.delta) @@ -702,7 +713,7 @@ impl Client { } ClientboundGamePacket::BlockUpdate(p) => { debug!("Got block update packet {:?}", p); - let mut dimension = client.dimension.lock(); + let mut dimension = client.dimension.write(); dimension.set_block_state(&p.pos, p.block_state); } ClientboundGamePacket::Animate(p) => { @@ -710,7 +721,7 @@ impl Client { } ClientboundGamePacket::SectionBlocksUpdate(p) => { debug!("Got section blocks update packet {:?}", p); - let mut dimension = client.dimension.lock(); + let mut dimension = client.dimension.write(); for state in &p.states { dimension.set_block_state(&(p.section_pos + state.pos.clone()), state.state); } @@ -808,8 +819,8 @@ impl Client { async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) { // return if there's no chunk at the player's position { - let dimension_lock = client.dimension.lock(); - let player_lock = client.player.lock(); + let dimension_lock = client.dimension.write(); + let player_lock = client.player.write(); let player_entity = player_lock.entity(&dimension_lock); let player_entity = if let Some(player_entity) = player_entity { player_entity @@ -835,30 +846,42 @@ impl Client { } /// Returns the entity associated to the player. - pub fn entity_mut<'d>(&self, dimension: &'d mut Dimension) -> EntityMut<'d> { + pub fn entity_mut(&self) -> Entity<RwLockWriteGuard<Dimension>> { let entity_id = { - let player_lock = self.player.lock(); + let player_lock = self.player.write(); player_lock.entity_id }; - dimension - .entity_mut(entity_id) - .expect("Player entity should be in the given dimension") + + let mut dimension = self.dimension.write(); + + let entity_data = dimension + .entity_storage + .get_mut_by_id(entity_id) + .expect("Player entity should exist"); + let entity_ptr = unsafe { entity_data.as_ptr() }; + Entity::new(dimension, entity_id, entity_ptr) } /// Returns the entity associated to the player. - pub fn entity<'d>(&self, dimension: &'d Dimension) -> EntityRef<'d> { + pub fn entity(&self) -> Entity<RwLockReadGuard<Dimension>> { let entity_id = { - let player_lock = self.player.lock(); + let player_lock = self.player.read(); player_lock.entity_id }; - dimension - .entity(entity_id) - .expect("Player entity should be in the given dimension") + + let dimension = self.dimension.read(); + + let entity_data = dimension + .entity_storage + .get_by_id(entity_id) + .expect("Player entity should be in the given dimension"); + let entity_ptr = unsafe { entity_data.as_const_ptr() }; + Entity::new(dimension, entity_id, entity_ptr) } /// Returns whether we have a received the login packet yet. pub fn logged_in(&self) -> bool { - let dimension = self.dimension.lock(); - let player = self.player.lock(); + let dimension = self.dimension.read(); + let player = self.player.write(); player.entity(&dimension).is_some() } diff --git a/azalea-client/src/get_mc_dir.rs b/azalea-client/src/get_mc_dir.rs index 440550a7..440550a7 100644..100755 --- a/azalea-client/src/get_mc_dir.rs +++ b/azalea-client/src/get_mc_dir.rs diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 7a76e103..544ea0f4 100755 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -5,6 +5,9 @@ //! [`azalea_protocol`]: https://crates.io/crates/azalea-protocol //! [`azalea`]: https://crates.io/crates/azalea +#![allow(incomplete_features)] +#![feature(trait_upcasting)] + mod account; mod chat; mod client; @@ -12,11 +15,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::MoveDirection; +pub use movement::{SprintDirection, WalkDirection}; pub use player::Player; +pub use plugins::{Plugin, Plugins}; #[cfg(test)] mod tests { diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index 93acf36f..145513c0 100644..100755 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -2,6 +2,7 @@ use crate::Client; use azalea_core::Vec3; use azalea_physics::collision::{MovableEntity, MoverType}; use azalea_physics::HasPhysics; +use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket; use azalea_protocol::packets::game::{ serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, @@ -28,24 +29,19 @@ impl From<MoveEntityError> for MovePlayerError { } impl Client { - /// This gets called every tick. - pub async fn send_position(&mut self) -> Result<(), MovePlayerError> { + /// This gets called automatically every tick. + pub(crate) async fn send_position(&mut self) -> Result<(), MovePlayerError> { let packet = { - let player_lock = self.player.lock(); + self.send_sprinting_if_needed().await?; + // TODO: the camera being able to be controlled by other entities isn't implemented yet + // if !self.is_controlled_camera() { return }; + let mut physics_state = self.physics_state.lock(); - let mut dimension_lock = self.dimension.lock(); - let mut player_entity = player_lock - .entity_mut(&mut dimension_lock) - .expect("Player must exist"); + let player_entity = self.entity(); let player_pos = player_entity.pos(); let player_old_pos = player_entity.last_pos; - // TODO: send sprinting and sneaking packets here if they changed - - // TODO: the camera being able to be controlled by other entities isn't implemented yet - // if !self.is_controlled_camera() { return }; - let x_delta = player_pos.x - player_old_pos.x; let y_delta = player_pos.y - player_old_pos.y; let z_delta = player_pos.z - player_old_pos.z; @@ -105,6 +101,9 @@ impl Client { None }; + drop(player_entity); + let mut player_entity = self.entity_mut(); + if sending_position { player_entity.last_pos = *player_entity.pos(); physics_state.position_remainder = 0; @@ -127,10 +126,35 @@ impl Client { Ok(()) } + async fn send_sprinting_if_needed(&mut self) -> Result<(), MovePlayerError> { + let is_sprinting = self.entity().metadata.sprinting; + let was_sprinting = self.physics_state.lock().was_sprinting; + if is_sprinting != was_sprinting { + let sprinting_action = if is_sprinting { + azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting + } else { + azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting + }; + let player_entity_id = self.entity().id; + self.write_packet( + ServerboundPlayerCommandPacket { + id: player_entity_id, + action: sprinting_action, + data: 0, + } + .get(), + ) + .await?; + self.physics_state.lock().was_sprinting = is_sprinting; + } + + Ok(()) + } + // Set our current position to the provided Vec3, potentially clipping through blocks. pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> { - let player_lock = self.player.lock(); - let mut dimension_lock = self.dimension.lock(); + let player_lock = self.player.write(); + let mut dimension_lock = self.dimension.write(); dimension_lock.set_entity_pos(player_lock.entity_id, new_pos)?; @@ -138,8 +162,8 @@ impl Client { } pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> { - let mut dimension_lock = self.dimension.lock(); - let player = self.player.lock(); + let mut dimension_lock = self.dimension.write(); + let player = self.player.write(); let mut entity = player .entity_mut(&mut dimension_lock) @@ -160,19 +184,38 @@ impl Client { pub fn ai_step(&mut self) { self.tick_controls(None); - let player_lock = self.player.lock(); - let mut dimension_lock = self.dimension.lock(); - let mut player_entity = player_lock - .entity_mut(&mut dimension_lock) - .expect("Player must exist"); - // server ai step { + let mut player_entity = self.entity_mut(); + let physics_state = self.physics_state.lock(); player_entity.xxa = physics_state.left_impulse; player_entity.zza = physics_state.forward_impulse; } + // TODO: food data and abilities + // let has_enough_food_to_sprint = self.food_data().food_level || self.abilities().may_fly; + let has_enough_food_to_sprint = true; + + // TODO: double tapping w to sprint i think + + let trying_to_sprint = self.physics_state.lock().trying_to_sprint; + + if !self.sprinting() + && ( + // !self.is_in_water() + // || self.is_underwater() && + self.has_enough_impulse_to_start_sprinting() + && has_enough_food_to_sprint + // && !self.using_item() + // && !self.has_effect(MobEffects.BLINDNESS) + && trying_to_sprint + ) + { + self.set_sprinting(true); + } + + let mut player_entity = self.entity_mut(); player_entity.ai_step(); } @@ -184,21 +227,21 @@ impl Client { let mut left_impulse: f32 = 0.; let move_direction = physics_state.move_direction; match move_direction { - MoveDirection::Forward | MoveDirection::ForwardRight | MoveDirection::ForwardLeft => { + WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => { forward_impulse += 1.; } - MoveDirection::Backward - | MoveDirection::BackwardRight - | MoveDirection::BackwardLeft => { + WalkDirection::Backward + | WalkDirection::BackwardRight + | WalkDirection::BackwardLeft => { forward_impulse -= 1.; } _ => {} }; match move_direction { - MoveDirection::Right | MoveDirection::ForwardRight | MoveDirection::BackwardRight => { + WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => { left_impulse += 1.; } - MoveDirection::Left | MoveDirection::ForwardLeft | MoveDirection::BackwardLeft => { + WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => { left_impulse -= 1.; } _ => {} @@ -212,35 +255,90 @@ impl Client { } } - /// Start walking in the given direction. - pub fn walk(&mut self, direction: MoveDirection) { + /// Start walking in the given direction. To sprint, use + /// [`Client::sprint`]. To stop walking, call walk with + /// `WalkDirection::None`. + pub fn walk(&mut self, direction: WalkDirection) { + { + let mut physics_state = self.physics_state.lock(); + physics_state.move_direction = direction; + } + + self.set_sprinting(false); + } + + /// Start sprinting in the given direction. To stop moving, call + /// [`Client::walk(WalkDirection::None)`] + pub fn sprint(&mut self, direction: SprintDirection) { let mut physics_state = self.physics_state.lock(); - physics_state.move_direction = direction; + physics_state.move_direction = WalkDirection::from(direction); + physics_state.trying_to_sprint = true; + } + + // Whether we're currently sprinting. + pub fn sprinting(&self) -> bool { + self.entity().metadata.sprinting + } + + /// Change whether we're sprinting by adding an attribute modifier to the + /// player. You should use the [`walk`] and [`sprint`] methods instead. + /// Returns if the operation was successful. + fn set_sprinting(&mut self, sprinting: bool) -> bool { + let mut player_entity = self.entity_mut(); + player_entity.metadata.sprinting = sprinting; + if sprinting { + player_entity + .attributes + .speed + .insert(azalea_world::entity::attributes::sprinting_modifier()) + .is_ok() + } else { + player_entity + .attributes + .speed + .remove(&azalea_world::entity::attributes::sprinting_modifier().uuid) + .is_none() + } } - /// Toggle whether we're jumping. This acts as if you held space in + /// Set whether we're jumping. This acts as if you held space in /// vanilla. If you want to jump once, use the `jump` function. /// /// If you're making a realistic client, calling this function every tick is /// recommended. pub fn set_jumping(&mut self, jumping: bool) { - let mut dimension = self.dimension.lock(); - let mut player_entity = self.entity_mut(&mut dimension); - + let mut player_entity = self.entity_mut(); player_entity.jumping = jumping; } /// Returns whether the player will try to jump next tick. pub fn jumping(&self) -> bool { - let dimension = self.dimension.lock(); - let player_entity = self.entity(&dimension); + let player_entity = self.entity(); player_entity.jumping } + + /// Sets your rotation. `y_rot` is yaw (looking to the side), `x_rot` is + /// pitch (looking up and down). You can get these numbers from the vanilla + /// f3 screen. + pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { + let mut player_entity = self.entity_mut(); + player_entity.set_rotation(y_rot, x_rot); + } + + // Whether the player is moving fast enough to be able to start sprinting. + fn has_enough_impulse_to_start_sprinting(&self) -> bool { + // if self.underwater() { + // self.has_forward_impulse() + // } else { + let physics_state = self.physics_state.lock(); + physics_state.forward_impulse > 0.8 + // } + } } #[derive(Clone, Copy, Debug, Default)] -pub enum MoveDirection { +pub enum WalkDirection { #[default] None, Forward, @@ -252,3 +350,21 @@ pub enum MoveDirection { BackwardRight, BackwardLeft, } + +/// The directions that we can sprint in. It's a subset of [`WalkDirection`]. +#[derive(Clone, Copy, Debug)] +pub enum SprintDirection { + Forward, + ForwardRight, + ForwardLeft, +} + +impl From<SprintDirection> for WalkDirection { + fn from(d: SprintDirection) -> Self { + match d { + SprintDirection::Forward => WalkDirection::Forward, + SprintDirection::ForwardRight => WalkDirection::ForwardRight, + SprintDirection::ForwardLeft => WalkDirection::ForwardLeft, + } + } +} diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index 11651b9c..ea6125ad 100644..100755 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -1,4 +1,4 @@ -use azalea_world::entity::{EntityMut, EntityRef}; +use azalea_world::entity::Entity; use azalea_world::Dimension; use uuid::Uuid; @@ -18,12 +18,12 @@ pub struct Player { impl Player { /// Get a reference to the entity of the player in the world. - pub fn entity<'d>(&'d self, dimension: &'d Dimension) -> Option<EntityRef> { + pub fn entity<'d>(&'d self, dimension: &'d Dimension) -> Option<Entity<&Dimension>> { dimension.entity(self.entity_id) } /// Get a mutable reference to the entity of the player in the world. - pub fn entity_mut<'d>(&'d self, dimension: &'d mut Dimension) -> Option<EntityMut> { + pub fn entity_mut<'d>(&'d self, dimension: &'d mut Dimension) -> Option<Entity> { dimension.entity_mut(self.entity_id) } diff --git a/azalea-client/src/plugins.rs b/azalea-client/src/plugins.rs new file mode 100644 index 00000000..1a3aa049 --- /dev/null +++ b/azalea-client/src/plugins.rs @@ -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_ref() as &dyn Any).downcast_ref::<T>()) + } +} + +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 + Any + '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() + } +} |
