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 should_mine(&mut self, block: BlockPos) -> bool {
116 let block_state = self
117 .instance
118 .read()
119 .get_block_state(&block)
120 .unwrap_or_default();
121 if is_block_state_passable(block_state) {
122 return false;
124 }
125
126 true
127 }
128
129 pub fn mine(&mut self, block: BlockPos) -> bool {
132 let block_state = self
133 .instance
134 .read()
135 .get_block_state(&block)
136 .unwrap_or_default();
137 if is_block_state_passable(block_state) {
138 return false;
140 }
141
142 let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
143
144 self.set_selected_hotbar_slot_events
145 .write(SetSelectedHotbarSlotEvent {
146 entity: self.entity,
147 slot: best_tool_result.index as u8,
148 });
149
150 self.is_currently_mining = true;
151
152 self.walk(WalkDirection::None);
153 self.look_at_exact(block.center());
154 self.start_mining_events.write(StartMiningBlockEvent {
155 entity: self.entity,
156 position: block,
157 });
158
159 true
160 }
161
162 pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
165 let horizontal_distance_from_start = (self.start.center() - self.position)
166 .horizontal_distance_squared()
167 .sqrt();
168 let at_start_position =
169 BlockPos::from(self.position) == self.start && horizontal_distance_from_start < 0.25;
170
171 if self.should_mine(block) {
172 if at_start_position {
173 self.look_at(block.center());
174 self.mine(block);
175 } else {
176 self.look_at(self.start.center());
177 self.walk(WalkDirection::Forward);
178 }
179 true
180 } else {
181 false
182 }
183 }
184
185 pub fn get_block_state(&self, block: BlockPos) -> BlockState {
186 self.instance
187 .read()
188 .get_block_state(&block)
189 .unwrap_or_default()
190 }
191}
192
193pub struct IsReachedCtx<'a> {
194 pub target: BlockPos,
196 pub start: BlockPos,
198 pub position: Vec3,
199 pub physics: &'a azalea_entity::Physics,
200}
201
202#[must_use]
205pub fn default_is_reached(
206 IsReachedCtx {
207 position,
208 target,
209 physics,
210 ..
211 }: IsReachedCtx,
212) -> bool {
213 if BlockPos::from(position) == target {
214 return true;
215 }
216
217 BlockPos::from(position).up(1) == target && physics.on_ground()
220}
221
222pub struct PathfinderCtx<'a> {
223 pub edges: &'a mut Vec<Edge>,
224 pub world: &'a CachedWorld,
225 pub mining_cache: &'a MiningCache,
226
227 pub custom_state: &'a CustomPathfinderStateRef,
228}