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