azalea/
auto_tool.rs

1use azalea_block::{BlockState, BlockTrait, fluid_state::FluidKind};
2use azalea_client::{Client, inventory::Inventory};
3use azalea_core::position::BlockPos;
4use azalea_entity::{FluidOnEyes, Physics};
5use azalea_inventory::{ItemStack, Menu, components};
6
7use crate::BotClientExt;
8
9#[derive(Debug)]
10pub struct BestToolResult {
11    pub index: usize,
12    pub percentage_per_tick: f32,
13}
14
15pub trait AutoToolClientExt {
16    fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult;
17    fn mine_with_auto_tool(&self, block_pos: BlockPos) -> impl Future<Output = ()> + Send;
18}
19
20impl AutoToolClientExt for Client {
21    fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult {
22        let mut ecs = self.ecs.lock();
23        let (inventory, physics, fluid_on_eyes) =
24            self.query::<(&Inventory, &Physics, &FluidOnEyes)>(&mut ecs);
25        let menu = &inventory.inventory_menu;
26
27        accurate_best_tool_in_hotbar_for_block(block, menu, physics, fluid_on_eyes)
28    }
29
30    async fn mine_with_auto_tool(&self, block_pos: BlockPos) {
31        let block_state = self
32            .world()
33            .read()
34            .get_block_state(block_pos)
35            .unwrap_or_default();
36        let best_tool_result = self.best_tool_in_hotbar_for_block(block_state);
37        self.set_selected_hotbar_slot(best_tool_result.index as u8);
38        self.mine(block_pos).await;
39    }
40}
41
42/// Returns the best tool in the hotbar for the given block.
43///
44/// Note that this doesn't take into account whether the player is on the ground
45/// or in water, use [`accurate_best_tool_in_hotbar_for_block`] instead if you
46/// care about those things.
47pub fn best_tool_in_hotbar_for_block(block: BlockState, menu: &Menu) -> BestToolResult {
48    let mut physics = Physics::default();
49    physics.set_on_ground(true);
50
51    accurate_best_tool_in_hotbar_for_block(
52        block,
53        menu,
54        &physics,
55        &FluidOnEyes::new(FluidKind::Empty),
56    )
57}
58
59pub fn accurate_best_tool_in_hotbar_for_block(
60    block: BlockState,
61    menu: &Menu,
62    physics: &Physics,
63    fluid_on_eyes: &FluidOnEyes,
64) -> BestToolResult {
65    let hotbar_slots = &menu.slots()[menu.hotbar_slots_range()];
66
67    let mut best_speed = 0.;
68    let mut best_slot = None;
69
70    let block = Box::<dyn BlockTrait>::from(block);
71    let registry_block = block.as_registry_block();
72
73    if matches!(
74        registry_block,
75        azalea_registry::Block::Water | azalea_registry::Block::Lava
76    ) {
77        // can't mine fluids
78        return BestToolResult {
79            index: 0,
80            percentage_per_tick: 0.,
81        };
82    }
83
84    // find the first slot that has an item without durability
85    for (i, item_slot) in hotbar_slots.iter().enumerate() {
86        let this_item_speed;
87        match item_slot {
88            ItemStack::Empty => {
89                this_item_speed = Some(azalea_entity::mining::get_mine_progress(
90                    block.as_ref(),
91                    azalea_registry::Item::Air,
92                    menu,
93                    fluid_on_eyes,
94                    physics,
95                ));
96            }
97            ItemStack::Present(item_stack) => {
98                // lazy way to avoid checking durability since azalea doesn't have durability
99                // data yet
100                if !item_stack.components.has::<components::Damage>() {
101                    this_item_speed = Some(azalea_entity::mining::get_mine_progress(
102                        block.as_ref(),
103                        item_stack.kind,
104                        menu,
105                        fluid_on_eyes,
106                        physics,
107                    ));
108                } else {
109                    this_item_speed = None;
110                }
111            }
112        }
113        if let Some(this_item_speed) = this_item_speed
114            && this_item_speed > best_speed
115        {
116            best_slot = Some(i);
117            best_speed = this_item_speed;
118        }
119    }
120
121    // now check every item
122    for (i, item_slot) in hotbar_slots.iter().enumerate() {
123        if let ItemStack::Present(item_slot) = item_slot {
124            let this_item_speed = azalea_entity::mining::get_mine_progress(
125                block.as_ref(),
126                item_slot.kind,
127                menu,
128                fluid_on_eyes,
129                physics,
130            );
131            if this_item_speed > best_speed {
132                best_slot = Some(i);
133                best_speed = this_item_speed;
134            }
135        }
136    }
137
138    BestToolResult {
139        index: best_slot.unwrap_or(0),
140        percentage_per_tick: best_speed,
141    }
142}