1pub mod basic;
2pub mod parkour;
3
4use std::{
5 fmt::{self, Debug},
6 sync::Arc,
7};
8
9use azalea_block::BlockState;
10use azalea_client::{
11 SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
12 inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent,
13};
14use azalea_core::position::{BlockPos, Vec3};
15use azalea_inventory::Menu;
16use azalea_world::Instance;
17use bevy_ecs::{entity::Entity, message::MessageWriter, system::Commands};
18use parking_lot::RwLock;
19use tracing::debug;
20
21use super::{
22 astar,
23 custom_state::CustomPathfinderStateRef,
24 mining::MiningCache,
25 rel_block_pos::RelBlockPos,
26 world::{CachedWorld, is_block_state_passable},
27};
28use crate::{
29 auto_tool::best_tool_in_hotbar_for_block,
30 bot::{JumpEvent, LookAtEvent},
31 pathfinder::player_pos_to_block_pos,
32};
33
34type Edge = astar::Edge<RelBlockPos, MoveData>;
35
36pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
37
38pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
39 basic::basic_move(ctx, node);
40 parkour::parkour_move(ctx, node);
41}
42
43#[derive(Clone)]
44pub struct MoveData {
45 pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
48 pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
50}
51impl Debug for MoveData {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_struct("MoveData")
54 .finish()
56 }
57}
58
59pub struct ExecuteCtx<'s, 'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
60 pub entity: Entity,
61 pub target: BlockPos,
63 pub start: BlockPos,
65 pub position: Vec3,
66 pub physics: &'a azalea_entity::Physics,
67 pub is_currently_mining: bool,
68 pub instance: Arc<RwLock<Instance>>,
69 pub menu: Menu,
70
71 pub commands: &'a mut Commands<'w1, 's>,
72 pub look_at_events: &'a mut MessageWriter<'w2, LookAtEvent>,
73 pub sprint_events: &'a mut MessageWriter<'w3, StartSprintEvent>,
74 pub walk_events: &'a mut MessageWriter<'w4, StartWalkEvent>,
75 pub jump_events: &'a mut MessageWriter<'w5, JumpEvent>,
76 pub start_mining_events: &'a mut MessageWriter<'w6, StartMiningBlockEvent>,
77}
78
79impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_, '_> {
80 pub fn look_at(&mut self, position: Vec3) {
81 self.look_at_events.write(LookAtEvent {
82 entity: self.entity,
83 position: Vec3 {
84 x: position.x,
85 y: self.position.up(1.53).y,
87 z: position.z,
88 },
89 });
90 }
91
92 pub fn look_at_exact(&mut self, position: Vec3) {
93 self.look_at_events.write(LookAtEvent {
94 entity: self.entity,
95 position,
96 });
97 }
98
99 pub fn sprint(&mut self, direction: SprintDirection) {
100 self.sprint_events.write(StartSprintEvent {
101 entity: self.entity,
102 direction,
103 });
104 }
105
106 pub fn walk(&mut self, direction: WalkDirection) {
107 self.walk_events.write(StartWalkEvent {
108 entity: self.entity,
109 direction,
110 });
111 }
112
113 pub fn jump(&mut self) {
114 self.jump_events.write(JumpEvent {
115 entity: self.entity,
116 });
117 }
118
119 pub fn jump_if_in_water(&mut self) {
120 if self.physics.is_in_water() {
121 self.jump();
122 }
123 }
124
125 pub fn should_mine(&mut self, block: BlockPos) -> bool {
127 let block_state = self
128 .instance
129 .read()
130 .get_block_state(block)
131 .unwrap_or_default();
132 if is_block_state_passable(block_state) {
133 return false;
135 }
136
137 true
138 }
139
140 pub fn mine(&mut self, block: BlockPos) -> bool {
143 let block_state = self
144 .instance
145 .read()
146 .get_block_state(block)
147 .unwrap_or_default();
148 if is_block_state_passable(block_state) {
149 return false;
151 }
152
153 let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
154 debug!("best tool for {block_state:?}: {best_tool_result:?}");
155
156 self.commands.trigger(SetSelectedHotbarSlotEvent {
157 entity: self.entity,
158 slot: best_tool_result.index as u8,
159 });
160
161 self.is_currently_mining = true;
162
163 self.walk(WalkDirection::None);
164 self.look_at_exact(block.center());
165 self.start_mining_events.write(StartMiningBlockEvent {
166 entity: self.entity,
167 position: block,
168 force: true,
169 });
170
171 true
172 }
173
174 pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
177 let horizontal_distance_from_start = (self.start.center() - self.position)
178 .horizontal_distance_squared()
179 .sqrt();
180 let at_start_position = player_pos_to_block_pos(self.position) == self.start
181 && horizontal_distance_from_start < 0.25;
182
183 if self.should_mine(block) {
184 if at_start_position {
185 self.look_at(block.center());
186 self.mine(block);
187 } else {
188 self.look_at(self.start.center());
189 self.walk(WalkDirection::Forward);
190 }
191 true
192 } else {
193 false
194 }
195 }
196
197 pub fn get_block_state(&self, block: BlockPos) -> BlockState {
198 self.instance
199 .read()
200 .get_block_state(block)
201 .unwrap_or_default()
202 }
203}
204
205pub struct IsReachedCtx<'a> {
206 pub target: BlockPos,
208 pub start: BlockPos,
210 pub position: Vec3,
211 pub physics: &'a azalea_entity::Physics,
212}
213
214#[must_use]
217pub fn default_is_reached(
218 IsReachedCtx {
219 position,
220 target,
221 physics,
222 ..
223 }: IsReachedCtx,
224) -> bool {
225 if BlockPos::from(position) == target {
226 return true;
227 }
228
229 BlockPos::from(position).up(1) == target && physics.on_ground()
232}
233
234pub struct PathfinderCtx<'a> {
235 pub edges: &'a mut Vec<Edge>,
236 pub world: &'a CachedWorld,
237 pub mining_cache: &'a MiningCache,
238
239 pub custom_state: &'a CustomPathfinderStateRef,
240}