1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +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
parent 04dd6dd0a4
commit abf995a702
7 changed files with 102 additions and 21 deletions

View file

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

View file

@ -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()];

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 {
/// 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)]
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,
}

View file

@ -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<Output = ()> + Send;
/// Wait for one ECS Update.
fn wait_one_update(&self) -> impl Future<Output = ()> + Send;
/// Wait for the specified number of game ticks.
fn wait_ticks(&self, n: usize) -> impl Future<Output = ()> + Send;
/// Wait for the specified number of ECS `Update`s.
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
/// 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;
}
}

View file

@ -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<Output = Option<ContainerHandle>> + Send;
fn open_inventory(&self) -> Option<ContainerHandle>;
fn get_held_item(&self) -> ItemStack;
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
/// 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>) {
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)]

View file

@ -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() {