diff --git a/azalea-block/src/range.rs b/azalea-block/src/range.rs index cbe77284..183c3cc0 100644 --- a/azalea-block/src/range.rs +++ b/azalea-block/src/range.rs @@ -1,6 +1,7 @@ use std::{ collections::{HashSet, hash_set}, ops::{Add, RangeInclusive}, + sync::LazyLock, }; use crate::{BlockState, block_state::BlockStateIntegerRepr}; diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index 829b37f8..23651db3 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -60,6 +60,36 @@ impl Client { let inventory = self.query::<&Inventory>(&mut ecs); inventory.menu().clone() } + + /// Returns the index of the hotbar slot that's currently selected. + /// + /// If you want to access the actual held item, you can get the current menu + /// with [`Client::menu`] and then get the slot index by offsetting from + /// the start of [`azalea_inventory::Menu::hotbar_slots_range`]. + /// + /// You can use [`Self::set_selected_hotbar_slot`] to change it. + pub fn selected_hotbar_slot(&self) -> u8 { + let mut ecs = self.ecs.lock(); + let inventory = self.query::<&Inventory>(&mut ecs); + inventory.selected_hotbar_slot + } + + /// Update the selected hotbar slot index. + /// + /// This will run next `Update`, so you might want to call + /// `bot.wait_updates(1)` after calling this if you're using `azalea`. + pub fn set_selected_hotbar_slot(&self, new_hotbar_slot_index: u8) { + assert!( + new_hotbar_slot_index < 9, + "Hotbar slot index must be in the range 0..=8" + ); + + let mut ecs = self.ecs.lock(); + ecs.send_event(SetSelectedHotbarSlotEvent { + entity: self.entity, + slot: new_hotbar_slot_index, + }); + } } /// A component present on all local players that have an inventory. @@ -499,7 +529,8 @@ impl Inventory { self.quick_craft_slots.clear(); } - /// Get the item in the player's hotbar that is currently being held. + /// Get the item in the player's hotbar that is currently being held in its + /// main hand. pub fn held_item(&self) -> ItemStack { let inventory = &self.inventory_menu; let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()]; diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs index 656cab1b..9f51ceaf 100644 --- a/azalea-core/src/direction.rs +++ b/azalea-core/src/direction.rs @@ -162,6 +162,16 @@ impl CardinalDirection { } } } +impl From for Direction { + fn from(value: CardinalDirection) -> Self { + match value { + CardinalDirection::North => Direction::North, + CardinalDirection::South => Direction::South, + CardinalDirection::West => Direction::West, + CardinalDirection::East => Direction::East, + } + } +} impl Axis { /// Pick x, y, or z from the arguments depending on the axis. diff --git a/azalea-inventory/src/operations.rs b/azalea-inventory/src/operations.rs index 90ad2403..f410c2c5 100644 --- a/azalea-inventory/src/operations.rs +++ b/azalea-inventory/src/operations.rs @@ -61,6 +61,8 @@ impl From for ClickOperation { #[derive(Debug, Clone)] pub struct SwapClick { pub source_slot: u16, + /// 0-8 for hotbar slots, 40 for offhand, everything else is treated as a + /// slot index. pub target_slot: u8, } diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 8bc9d594..9e8566bf 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -92,10 +92,10 @@ pub trait BotClientExt { fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; /// Get a receiver that will receive a message every ECS Update. fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; - /// Wait for one tick. - fn wait_one_tick(&self) -> impl Future + Send; - /// Wait for one ECS Update. - fn wait_one_update(&self) -> impl Future + Send; + /// Wait for the specified number of game ticks. + fn wait_ticks(&self, n: usize) -> impl Future + Send; + /// Wait for the specified number of ECS `Update`s. + fn wait_updates(&self, n: usize) -> impl Future + Send; /// Mine a block. This won't turn the bot's head towards the block, so if /// that's necessary you'll have to do that yourself with [`look_at`]. /// @@ -156,23 +156,32 @@ impl BotClientExt for azalea_client::Client { update_broadcast.subscribe() } - /// Wait for one tick using [`Self::get_tick_broadcaster`]. + /// Wait for the specified number of ticks using + /// [`Self::get_tick_broadcaster`]. /// /// If you're going to run this in a loop, you may want to use that function - /// instead and use the `Receiver` from it as it'll be more efficient. - async fn wait_one_tick(&self) { + /// instead and use the `Receiver` from it to avoid accidentally skipping + /// ticks and having to wait longer. + async fn wait_ticks(&self, n: usize) { let mut receiver = self.get_tick_broadcaster(); - // wait for the next tick - let _ = receiver.recv().await; + for _ in 0..n { + let _ = receiver.recv().await; + } } - /// Waits for one ECS Update using [`Self::get_update_broadcaster`]. + /// Waits for the specified number of ECS `Update`s using + /// [`Self::get_update_broadcaster`]. + /// + /// These are basically equivalent to frames because even though we have no + /// rendering, some game mechanics depend on frames. /// /// If you're going to run this in a loop, you may want to use that function - /// instead and use the `Receiver` from it as it'll be more efficient. - async fn wait_one_update(&self) { + /// instead and use the `Receiver` from it to avoid accidentally skipping + /// ticks and having to wait longer. + async fn wait_updates(&self, n: usize) { let mut receiver = self.get_update_broadcaster(); - // wait for the next tick - let _ = receiver.recv().await; + for _ in 0..n { + let _ = receiver.recv().await; + } } async fn mine(&self, position: BlockPos) { @@ -221,10 +230,7 @@ fn look_at_listener( if let Ok((position, eye_height, mut look_direction)) = query.get_mut(event.entity) { let new_look_direction = direction_looking_at(&position.up(eye_height.into()), &event.position); - trace!( - "look at {:?} (currently at {:?})", - event.position, **position - ); + trace!("look at {} (currently at {})", event.position, **position); *look_direction = new_look_direction; } } diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 6715cd63..e5896d8a 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -6,7 +6,10 @@ use azalea_client::{ packet::game::ReceiveGamePacketEvent, }; use azalea_core::position::BlockPos; -use azalea_inventory::{ItemStack, Menu, operations::ClickOperation}; +use azalea_inventory::{ + ItemStack, Menu, + operations::{ClickOperation, PickupClick, QuickMoveClick}, +}; use azalea_protocol::packets::game::ClientboundGamePacket; use bevy_app::{App, Plugin, Update}; use bevy_ecs::{component::Component, prelude::EventReader, system::Commands}; @@ -27,6 +30,7 @@ pub trait ContainerClientExt { pos: BlockPos, ) -> impl Future> + Send; fn open_inventory(&self) -> Option; + fn get_held_item(&self) -> ItemStack; fn get_open_container(&self) -> Option; } @@ -93,6 +97,14 @@ impl ContainerClientExt for Client { } } + /// Get the item in the bot's hotbar that is currently being held in its + /// main hand. + fn get_held_item(&self) -> ItemStack { + let ecs = self.ecs.lock(); + let inventory = ecs.get::(self.entity).expect("no inventory"); + inventory.held_item() + } + /// Get a handle to the open container. This will return None if no /// container is open. This will not close the container when it's dropped. /// @@ -228,6 +240,25 @@ impl ContainerHandle { pub fn click(&self, operation: impl Into) { self.0.click(operation); } + + /// A shortcut for [`Self::click`] with `PickupClick::Left`. + pub fn left_click(&self, slot: impl Into) { + self.click(PickupClick::Left { + slot: Some(slot.into() as u16), + }); + } + /// A shortcut for [`Self::click`] with `QuickMoveClick::Left`. + pub fn shift_click(&self, slot: impl Into) { + self.click(QuickMoveClick::Left { + slot: slot.into() as u16, + }); + } + /// A shortcut for [`Self::click`] with `PickupClick::Right`. + pub fn right_click(&self, slot: impl Into) { + self.click(PickupClick::Right { + slot: Some(slot.into() as u16), + }); + } } #[derive(Component, Debug)] diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 08c72f9a..5ee56643 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -260,7 +260,7 @@ impl PathfinderClientExt for azalea_client::Client { async fn wait_until_goto_target_reached(&self) { // we do this to make sure the event got handled before we start checking // is_goto_target_reached - self.wait_one_update().await; + self.wait_updates(1).await; let mut tick_broadcaster = self.get_tick_broadcaster(); while !self.is_goto_target_reached() {