aboutsummaryrefslogtreecommitdiff
path: root/azalea/src
diff options
context:
space:
mode:
Diffstat (limited to 'azalea/src')
-rw-r--r--azalea/src/bot.rs18
-rw-r--r--azalea/src/container.rs140
-rw-r--r--azalea/src/lib.rs5
-rw-r--r--azalea/src/pathfinder/mod.rs4
-rw-r--r--azalea/src/prelude.rs5
-rw-r--r--azalea/src/swarm/mod.rs6
6 files changed, 165 insertions, 13 deletions
diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs
index a45ae28d..e5ea4c28 100644
--- a/azalea/src/bot.rs
+++ b/azalea/src/bot.rs
@@ -1,4 +1,5 @@
use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
+use crate::container::ContainerPlugin;
use crate::ecs::{
component::Component,
entity::Entity,
@@ -9,7 +10,8 @@ use crate::ecs::{
};
use azalea_core::Vec3;
use azalea_physics::{force_jump_listener, PhysicsSet};
-use azalea_world::entity::{metadata::Player, set_rotation, Jumping, Local, Physics, Position};
+use azalea_world::entity::{clamp_look_direction, EyeHeight, LookDirection};
+use azalea_world::entity::{metadata::Player, Jumping, Local, Position};
use std::f64::consts::PI;
use crate::pathfinder::PathfinderPlugin;
@@ -22,7 +24,9 @@ impl Plugin for BotPlugin {
.add_event::<JumpEvent>()
.add_systems((
insert_bot,
- look_at_listener.before(force_jump_listener),
+ look_at_listener
+ .before(force_jump_listener)
+ .before(clamp_look_direction),
jump_listener,
stop_jumping
.in_schedule(CoreSchedule::FixedUpdate)
@@ -99,12 +103,13 @@ pub struct LookAtEvent {
}
fn look_at_listener(
mut events: EventReader<LookAtEvent>,
- mut query: Query<(&Position, &mut Physics)>,
+ mut query: Query<(&Position, &EyeHeight, &mut LookDirection)>,
) {
for event in events.iter() {
- if let Ok((position, mut physics)) = query.get_mut(event.entity) {
- let (y_rot, x_rot) = direction_looking_at(position, &event.position);
- set_rotation(&mut physics, y_rot, x_rot);
+ if let Ok((position, eye_height, mut look_direction)) = query.get_mut(event.entity) {
+ let (y_rot, x_rot) =
+ direction_looking_at(&position.up(eye_height.into()), &event.position);
+ (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
}
}
}
@@ -129,5 +134,6 @@ impl PluginGroup for DefaultBotPlugins {
PluginGroupBuilder::start::<Self>()
.add(BotPlugin)
.add(PathfinderPlugin)
+ .add(ContainerPlugin)
}
}
diff --git a/azalea/src/container.rs b/azalea/src/container.rs
new file mode 100644
index 00000000..0016caad
--- /dev/null
+++ b/azalea/src/container.rs
@@ -0,0 +1,140 @@
+use std::fmt::Formatter;
+
+use azalea_client::{
+ inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent},
+ packet_handling::PacketEvent,
+ Client, TickBroadcast,
+};
+use azalea_core::BlockPos;
+use azalea_inventory::{operations::ClickOperation, ItemSlot, Menu};
+use azalea_protocol::packets::game::ClientboundGamePacket;
+use bevy_app::{App, Plugin};
+use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
+use std::fmt::Debug;
+
+pub struct ContainerPlugin;
+impl Plugin for ContainerPlugin {
+ fn build(&self, app: &mut App) {
+ app.add_system(handle_menu_opened_event);
+ }
+}
+
+pub trait ContainerClientExt {
+ async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle>;
+}
+
+impl ContainerClientExt for Client {
+ /// Open a container in the world, like a chest.
+ ///
+ /// ```
+ /// # use azalea::prelude::*;
+ /// # async fn example(mut bot: azalea::Client) {
+ /// let target_pos = bot
+ /// .world()
+ /// .read()
+ /// .find_block(bot.position(), &azalea::Block::Chest.into());
+ /// let Some(target_pos) = target_pos else {
+ /// bot.chat("no chest found");
+ /// return;
+ /// };
+ /// let container = bot.open_container(target_pos).await;
+ /// # }
+ /// ```
+ async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle> {
+ self.ecs
+ .lock()
+ .entity_mut(self.entity)
+ .insert(WaitingForInventoryOpen);
+ self.block_interact(pos);
+
+ let mut receiver = {
+ let ecs = self.ecs.lock();
+ let tick_broadcast = ecs.resource::<TickBroadcast>();
+ tick_broadcast.subscribe()
+ };
+ while receiver.recv().await.is_ok() {
+ let ecs = self.ecs.lock();
+ if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
+ break;
+ }
+ }
+
+ let ecs = self.ecs.lock();
+ let inventory = ecs
+ .get::<InventoryComponent>(self.entity)
+ .expect("no inventory");
+ if inventory.id == 0 {
+ None
+ } else {
+ Some(ContainerHandle {
+ id: inventory.id,
+ client: self.clone(),
+ })
+ }
+ }
+}
+
+/// A handle to the open container. The container will be closed once this is
+/// dropped.
+pub struct ContainerHandle {
+ pub id: u8,
+ client: Client,
+}
+impl Drop for ContainerHandle {
+ fn drop(&mut self) {
+ self.client.ecs.lock().send_event(CloseContainerEvent {
+ entity: self.client.entity,
+ id: self.id,
+ });
+ }
+}
+impl Debug for ContainerHandle {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ContainerHandle")
+ .field("id", &self.id)
+ .finish()
+ }
+}
+impl ContainerHandle {
+ /// Returns the menu of the container. If the container is closed, this
+ /// will return `None`.
+ pub fn menu(&self) -> Option<Menu> {
+ let ecs = self.client.ecs.lock();
+ let inventory = ecs
+ .get::<InventoryComponent>(self.client.entity)
+ .expect("no inventory");
+ if inventory.id == self.id {
+ Some(inventory.container_menu.clone().unwrap())
+ } else {
+ None
+ }
+ }
+
+ /// Returns the item slots in the container, not including the player's
+ /// inventory. If the container is closed, this will return `None`.
+ pub fn contents(&self) -> Option<Vec<ItemSlot>> {
+ self.menu().map(|menu| menu.contents())
+ }
+
+ pub fn click(&self, operation: impl Into<ClickOperation>) {
+ let operation = operation.into();
+ self.client.ecs.lock().send_event(ContainerClickEvent {
+ entity: self.client.entity,
+ window_id: self.id,
+ operation,
+ });
+ }
+}
+
+#[derive(Component, Debug)]
+pub struct WaitingForInventoryOpen;
+
+fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader<PacketEvent>) {
+ for event in events.iter() {
+ if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet {
+ commands
+ .entity(event.entity)
+ .remove::<WaitingForInventoryOpen>();
+ }
+ }
+}
diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs
index bd1d356a..2e8e4fa1 100644
--- a/azalea/src/lib.rs
+++ b/azalea/src/lib.rs
@@ -1,7 +1,10 @@
#![doc = include_str!("../README.md")]
#![feature(async_closure)]
+#![allow(incomplete_features)]
+#![feature(async_fn_in_trait)]
mod bot;
+mod container;
pub mod pathfinder;
pub mod prelude;
pub mod swarm;
@@ -12,7 +15,7 @@ pub use azalea_block as blocks;
pub use azalea_client::*;
pub use azalea_core::{BlockPos, Vec3};
pub use azalea_protocol as protocol;
-pub use azalea_registry::EntityKind;
+pub use azalea_registry::{Block, EntityKind, Item};
pub use azalea_world::{entity, Instance};
use bot::DefaultBotPlugins;
use ecs::component::Component;
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 9c06ebb8..56c8e0ce 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -93,7 +93,7 @@ fn goto_listener(
mut commands: Commands,
mut events: EventReader<GotoEvent>,
mut query: Query<(&Position, &WorldName)>,
- world_container: Res<InstanceContainer>,
+ instance_container: Res<InstanceContainer>,
) {
let thread_pool = AsyncComputeTaskPool::get();
@@ -106,7 +106,7 @@ fn goto_listener(
vertical_vel: VerticalVel::None,
};
- let world_lock = world_container
+ let world_lock = instance_container
.get(world_name)
.expect("Entity tried to pathfind but the entity isn't in a valid world");
let end = event.goal.goal_node();
diff --git a/azalea/src/prelude.rs b/azalea/src/prelude.rs
index b1a1fed3..87cb0b53 100644
--- a/azalea/src/prelude.rs
+++ b/azalea/src/prelude.rs
@@ -1,7 +1,10 @@
//! The Azalea prelude. Things that are necessary for a bare-bones bot are
//! re-exported here.
-pub use crate::{bot::BotClientExt, pathfinder::PathfinderClientExt, ClientBuilder};
+pub use crate::{
+ bot::BotClientExt, container::ContainerClientExt, pathfinder::PathfinderClientExt,
+ ClientBuilder,
+};
pub use azalea_client::{Account, Client, Event};
// this is necessary to make the macros that reference bevy_ecs work
pub use crate::ecs as bevy_ecs;
diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs
index 6fe11b7d..2253f5bd 100644
--- a/azalea/src/swarm/mod.rs
+++ b/azalea/src/swarm/mod.rs
@@ -37,7 +37,7 @@ pub struct Swarm {
// bot_datas: Arc<Mutex<Vec<(Client, S)>>>,
resolved_address: SocketAddr,
address: ServerAddress,
- pub world_container: Arc<RwLock<InstanceContainer>>,
+ pub instance_container: Arc<RwLock<InstanceContainer>>,
bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
@@ -248,7 +248,7 @@ where
// resolve the address
let resolved_address = resolver::resolve_address(&address).await?;
- let world_container = Arc::new(RwLock::new(InstanceContainer::default()));
+ let instance_container = Arc::new(RwLock::new(InstanceContainer::default()));
// we can't modify the swarm plugins after this
let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
@@ -263,7 +263,7 @@ where
resolved_address,
address,
- world_container,
+ instance_container,
bots_tx,