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