Skip to main content

azalea/
auto_tool.rs

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