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, event::EventWriter};
18use parking_lot::RwLock;
19
20use super::{
21 astar,
22 custom_state::CustomPathfinderStateRef,
23 mining::MiningCache,
24 rel_block_pos::RelBlockPos,
25 world::{CachedWorld, is_block_state_passable},
26};
27use crate::{JumpEvent, LookAtEvent, auto_tool::best_tool_in_hotbar_for_block};
28
29type Edge = astar::Edge<RelBlockPos, MoveData>;
30
31pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
32
33pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
34 basic::basic_move(ctx, node);
35 parkour::parkour_move(ctx, node);
36}
37
38#[derive(Clone)]
39pub struct MoveData {
40 pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
43 pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
45}
46impl Debug for MoveData {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.debug_struct("MoveData")
49 .finish()
51 }
52}
53
54pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
55 pub entity: Entity,
56 pub target: BlockPos,
58 pub start: BlockPos,
60 pub position: Vec3,
61 pub physics: &'a azalea_entity::Physics,
62 pub is_currently_mining: bool,
63 pub instance: Arc<RwLock<Instance>>,
64 pub menu: Menu,
65
66 pub look_at_events: &'a mut EventWriter<'w1, LookAtEvent>,
67 pub sprint_events: &'a mut EventWriter<'w2, StartSprintEvent>,
68 pub walk_events: &'a mut EventWriter<'w3, StartWalkEvent>,
69 pub jump_events: &'a mut EventWriter<'w4, JumpEvent>,
70 pub start_mining_events: &'a mut EventWriter<'w5, StartMiningBlockEvent>,
71 pub set_selected_hotbar_slot_events: &'a mut EventWriter<'w6, SetSelectedHotbarSlotEvent>,
72}
73
74impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_> {
75 pub fn look_at(&mut self, position: Vec3) {
76 self.look_at_events.write(LookAtEvent {
77 entity: self.entity,
78 position: Vec3 {
79 x: position.x,
80 y: self.position.up(1.53).y,
82 z: position.z,
83 },
84 });
85 }
86
87 pub fn look_at_exact(&mut self, position: Vec3) {
88 self.look_at_events.write(LookAtEvent {
89 entity: self.entity,
90 position,
91 });
92 }
93
94 pub fn sprint(&mut self, direction: SprintDirection) {
95 self.sprint_events.write(StartSprintEvent {
96 entity: self.entity,
97 direction,
98 });
99 }
100
101 pub fn walk(&mut self, direction: WalkDirection) {
102 self.walk_events.write(StartWalkEvent {
103 entity: self.entity,
104 direction,
105 });
106 }
107
108 pub fn jump(&mut self) {
109 self.jump_events.write(JumpEvent {
110 entity: self.entity,
111 });
112 }
113
114 pub fn jump_if_in_water(&mut self) {
115 if self.physics.is_in_water() {
116 self.jump();
117 }
118 }
119
120 pub fn should_mine(&mut self, block: BlockPos) -> bool {
122 let block_state = self
123 .instance
124 .read()
125 .get_block_state(block)
126 .unwrap_or_default();
127 if is_block_state_passable(block_state) {
128 return false;
130 }
131
132 true
133 }
134
135 pub fn mine(&mut self, block: BlockPos) -> bool {
138 let block_state = self
139 .instance
140 .read()
141 .get_block_state(block)
142 .unwrap_or_default();
143 if is_block_state_passable(block_state) {
144 return false;
146 }
147
148 let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
149
150 self.set_selected_hotbar_slot_events
151 .write(SetSelectedHotbarSlotEvent {
152 entity: self.entity,
153 slot: best_tool_result.index as u8,
154 });
155
156 self.is_currently_mining = true;
157
158 self.walk(WalkDirection::None);
159 self.look_at_exact(block.center());
160 self.start_mining_events.write(StartMiningBlockEvent {
161 entity: self.entity,
162 position: block,
163 });
164
165 true
166 }
167
168 pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
171 let horizontal_distance_from_start = (self.start.center() - self.position)
172 .horizontal_distance_squared()
173 .sqrt();
174 let at_start_position =
175 BlockPos::from(self.position) == self.start && horizontal_distance_from_start < 0.25;
176
177 if self.should_mine(block) {
178 if at_start_position {
179 self.look_at(block.center());
180 self.mine(block);
181 } else {
182 self.look_at(self.start.center());
183 self.walk(WalkDirection::Forward);
184 }
185 true
186 } else {
187 false
188 }
189 }
190
191 pub fn get_block_state(&self, block: BlockPos) -> BlockState {
192 self.instance
193 .read()
194 .get_block_state(block)
195 .unwrap_or_default()
196 }
197}
198
199pub struct IsReachedCtx<'a> {
200 pub target: BlockPos,
202 pub start: BlockPos,
204 pub position: Vec3,
205 pub physics: &'a azalea_entity::Physics,
206}
207
208#[must_use]
211pub fn default_is_reached(
212 IsReachedCtx {
213 position,
214 target,
215 physics,
216 ..
217 }: IsReachedCtx,
218) -> bool {
219 if BlockPos::from(position) == target {
220 return true;
221 }
222
223 BlockPos::from(position).up(1) == target && physics.on_ground()
226}
227
228pub struct PathfinderCtx<'a> {
229 pub edges: &'a mut Vec<Edge>,
230 pub world: &'a CachedWorld,
231 pub mining_cache: &'a MiningCache,
232
233 pub custom_state: &'a CustomPathfinderStateRef,
234}