azalea/
auto_tool.rs

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