1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 23:44:38 +00:00

replace wait_one_tick with wait_ticks and some other api improvements

This commit is contained in:
mat 2025-06-03 03:48:36 +05:00
commit abf995a702
7 changed files with 102 additions and 21 deletions

View file

@ -1,6 +1,7 @@
use std::{ use std::{
collections::{HashSet, hash_set}, collections::{HashSet, hash_set},
ops::{Add, RangeInclusive}, ops::{Add, RangeInclusive},
sync::LazyLock,
}; };
use crate::{BlockState, block_state::BlockStateIntegerRepr}; use crate::{BlockState, block_state::BlockStateIntegerRepr};

View file

@ -60,6 +60,36 @@ impl Client {
let inventory = self.query::<&Inventory>(&mut ecs); let inventory = self.query::<&Inventory>(&mut ecs);
inventory.menu().clone() 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. /// A component present on all local players that have an inventory.
@ -499,7 +529,8 @@ impl Inventory {
self.quick_craft_slots.clear(); 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 { pub fn held_item(&self) -> ItemStack {
let inventory = &self.inventory_menu; let inventory = &self.inventory_menu;
let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()]; let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()];

View file

@ -162,6 +162,16 @@ impl CardinalDirection {
} }
} }
} }
impl From<CardinalDirection> 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 { impl Axis {
/// Pick x, y, or z from the arguments depending on the axis. /// Pick x, y, or z from the arguments depending on the axis.

View file

@ -61,6 +61,8 @@ impl From<QuickMoveClick> for ClickOperation {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SwapClick { pub struct SwapClick {
pub source_slot: u16, pub source_slot: u16,
/// 0-8 for hotbar slots, 40 for offhand, everything else is treated as a
/// slot index.
pub target_slot: u8, pub target_slot: u8,
} }

View file

@ -92,10 +92,10 @@ pub trait BotClientExt {
fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
/// Get a receiver that will receive a message every ECS Update. /// Get a receiver that will receive a message every ECS Update.
fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>;
/// Wait for one tick. /// Wait for the specified number of game ticks.
fn wait_one_tick(&self) -> impl Future<Output = ()> + Send; fn wait_ticks(&self, n: usize) -> impl Future<Output = ()> + Send;
/// Wait for one ECS Update. /// Wait for the specified number of ECS `Update`s.
fn wait_one_update(&self) -> impl Future<Output = ()> + Send; fn wait_updates(&self, n: usize) -> impl Future<Output = ()> + Send;
/// Mine a block. This won't turn the bot's head towards the block, so if /// 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`]. /// 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() 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 /// 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. /// instead and use the `Receiver` from it to avoid accidentally skipping
async fn wait_one_tick(&self) { /// ticks and having to wait longer.
async fn wait_ticks(&self, n: usize) {
let mut receiver = self.get_tick_broadcaster(); let mut receiver = self.get_tick_broadcaster();
// wait for the next tick for _ in 0..n {
let _ = receiver.recv().await; 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 /// 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. /// instead and use the `Receiver` from it to avoid accidentally skipping
async fn wait_one_update(&self) { /// ticks and having to wait longer.
async fn wait_updates(&self, n: usize) {
let mut receiver = self.get_update_broadcaster(); let mut receiver = self.get_update_broadcaster();
// wait for the next tick for _ in 0..n {
let _ = receiver.recv().await; let _ = receiver.recv().await;
}
} }
async fn mine(&self, position: BlockPos) { 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) { if let Ok((position, eye_height, mut look_direction)) = query.get_mut(event.entity) {
let new_look_direction = let new_look_direction =
direction_looking_at(&position.up(eye_height.into()), &event.position); direction_looking_at(&position.up(eye_height.into()), &event.position);
trace!( trace!("look at {} (currently at {})", event.position, **position);
"look at {:?} (currently at {:?})",
event.position, **position
);
*look_direction = new_look_direction; *look_direction = new_look_direction;
} }
} }

View file

@ -6,7 +6,10 @@ use azalea_client::{
packet::game::ReceiveGamePacketEvent, packet::game::ReceiveGamePacketEvent,
}; };
use azalea_core::position::BlockPos; 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 azalea_protocol::packets::game::ClientboundGamePacket;
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::{component::Component, prelude::EventReader, system::Commands}; use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
@ -27,6 +30,7 @@ pub trait ContainerClientExt {
pos: BlockPos, pos: BlockPos,
) -> impl Future<Output = Option<ContainerHandle>> + Send; ) -> impl Future<Output = Option<ContainerHandle>> + Send;
fn open_inventory(&self) -> Option<ContainerHandle>; fn open_inventory(&self) -> Option<ContainerHandle>;
fn get_held_item(&self) -> ItemStack;
fn get_open_container(&self) -> Option<ContainerHandleRef>; fn get_open_container(&self) -> Option<ContainerHandleRef>;
} }
@ -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::<Inventory>(self.entity).expect("no inventory");
inventory.held_item()
}
/// Get a handle to the open container. This will return None if no /// 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. /// 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<ClickOperation>) { pub fn click(&self, operation: impl Into<ClickOperation>) {
self.0.click(operation); self.0.click(operation);
} }
/// A shortcut for [`Self::click`] with `PickupClick::Left`.
pub fn left_click(&self, slot: impl Into<usize>) {
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<usize>) {
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<usize>) {
self.click(PickupClick::Right {
slot: Some(slot.into() as u16),
});
}
} }
#[derive(Component, Debug)] #[derive(Component, Debug)]

View file

@ -260,7 +260,7 @@ impl PathfinderClientExt for azalea_client::Client {
async fn wait_until_goto_target_reached(&self) { async fn wait_until_goto_target_reached(&self) {
// we do this to make sure the event got handled before we start checking // we do this to make sure the event got handled before we start checking
// is_goto_target_reached // is_goto_target_reached
self.wait_one_update().await; self.wait_updates(1).await;
let mut tick_broadcaster = self.get_tick_broadcaster(); let mut tick_broadcaster = self.get_tick_broadcaster();
while !self.is_goto_target_reached() { while !self.is_goto_target_reached() {