azalea/
auto_tool.rs

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