aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-11-12 23:54:05 -0600
committerGitHub <noreply@github.com>2022-11-12 23:54:05 -0600
commit6eee543a3367d38a6f0e9bffb457a2bd76a8f9cc (patch)
treea5e493ccd7ec24293b8d866242c3836146517122 /azalea-client/src
parentfa57d03627aa20b1df44caed7cb025b6db1d9b53 (diff)
downloadazalea-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.rs0
-rwxr-xr-x[-rw-r--r--]azalea-client/src/chat.rs2
-rw-r--r--azalea-client/src/client.rs97
-rwxr-xr-x[-rw-r--r--]azalea-client/src/get_mc_dir.rs0
-rwxr-xr-xazalea-client/src/lib.rs7
-rwxr-xr-x[-rw-r--r--]azalea-client/src/movement.rs192
-rwxr-xr-x[-rw-r--r--]azalea-client/src/player.rs6
-rw-r--r--azalea-client/src/plugins.rs78
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()
+ }
+}