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
56pub 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 return BestToolResult {
95 index: 0,
96 percentage_per_tick: 0.,
97 };
98 }
99
100 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 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 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}