1pub mod basic;
2pub mod parkour;
3
4use std::{fmt::Debug, sync::Arc};
5
6use azalea_client::{
7 inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent, SprintDirection,
8 StartSprintEvent, StartWalkEvent, WalkDirection,
9};
10use azalea_core::position::{BlockPos, Vec3};
11use azalea_inventory::Menu;
12use azalea_world::Instance;
13use bevy_ecs::{entity::Entity, event::EventWriter};
14use parking_lot::RwLock;
15
16use super::{
17 astar,
18 mining::MiningCache,
19 rel_block_pos::RelBlockPos,
20 world::{is_block_state_passable, CachedWorld},
21};
22use crate::{auto_tool::best_tool_in_hotbar_for_block, JumpEvent, LookAtEvent};
23
24type Edge = astar::Edge<RelBlockPos, MoveData>;
25
26pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
27
28pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
29 basic::basic_move(ctx, node);
30 parkour::parkour_move(ctx, node);
31}
32
33#[derive(Clone)]
34pub struct MoveData {
35 pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
38 pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
40}
41impl Debug for MoveData {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("MoveData")
44 .finish()
46 }
47}
48
49pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
50 pub entity: Entity,
51 pub target: BlockPos,
53 pub start: BlockPos,
55 pub position: Vec3,
56 pub physics: &'a azalea_entity::Physics,
57 pub is_currently_mining: bool,
58 pub instance: Arc<RwLock<Instance>>,
59 pub menu: Menu,
60
61 pub look_at_events: &'a mut EventWriter<'w1, LookAtEvent>,
62 pub sprint_events: &'a mut EventWriter<'w2, StartSprintEvent>,
63 pub walk_events: &'a mut EventWriter<'w3, StartWalkEvent>,
64 pub jump_events: &'a mut EventWriter<'w4, JumpEvent>,
65 pub start_mining_events: &'a mut EventWriter<'w5, StartMiningBlockEvent>,
66 pub set_selected_hotbar_slot_events: &'a mut EventWriter<'w6, SetSelectedHotbarSlotEvent>,
67}
68
69impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_> {
70 pub fn look_at(&mut self, position: Vec3) {
71 self.look_at_events.send(LookAtEvent {
72 entity: self.entity,
73 position: Vec3 {
74 x: position.x,
75 y: self.position.up(1.53).y,
77 z: position.z,
78 },
79 });
80 }
81
82 pub fn look_at_exact(&mut self, position: Vec3) {
83 self.look_at_events.send(LookAtEvent {
84 entity: self.entity,
85 position,
86 });
87 }
88
89 pub fn sprint(&mut self, direction: SprintDirection) {
90 self.sprint_events.send(StartSprintEvent {
91 entity: self.entity,
92 direction,
93 });
94 }
95
96 pub fn walk(&mut self, direction: WalkDirection) {
97 self.walk_events.send(StartWalkEvent {
98 entity: self.entity,
99 direction,
100 });
101 }
102
103 pub fn jump(&mut self) {
104 self.jump_events.send(JumpEvent {
105 entity: self.entity,
106 });
107 }
108
109 pub fn should_mine(&mut self, block: BlockPos) -> bool {
111 let block_state = self
112 .instance
113 .read()
114 .get_block_state(&block)
115 .unwrap_or_default();
116 if is_block_state_passable(block_state) {
117 return false;
119 }
120
121 true
122 }
123
124 pub fn 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 let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
138
139 self.set_selected_hotbar_slot_events
140 .send(SetSelectedHotbarSlotEvent {
141 entity: self.entity,
142 slot: best_tool_result.index as u8,
143 });
144
145 self.is_currently_mining = true;
146
147 self.walk(WalkDirection::None);
148 self.look_at_exact(block.center());
149 self.start_mining_events.send(StartMiningBlockEvent {
150 entity: self.entity,
151 position: block,
152 });
153
154 true
155 }
156
157 pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
160 let horizontal_distance_from_start = (self.start.center() - self.position)
161 .horizontal_distance_squared()
162 .sqrt();
163 let at_start_position =
164 BlockPos::from(self.position) == self.start && horizontal_distance_from_start < 0.25;
165
166 if self.should_mine(block) {
167 if at_start_position {
168 self.mine(block);
169 } else {
170 self.look_at(self.start.center());
171 self.walk(WalkDirection::Forward);
172 }
173 true
174 } else {
175 false
176 }
177 }
178}
179
180pub struct IsReachedCtx<'a> {
181 pub target: BlockPos,
183 pub start: BlockPos,
185 pub position: Vec3,
186 pub physics: &'a azalea_entity::Physics,
187}
188
189#[must_use]
192pub fn default_is_reached(
193 IsReachedCtx {
194 position, target, ..
195 }: IsReachedCtx,
196) -> bool {
197 BlockPos::from(position) == target
198}
199
200pub struct PathfinderCtx<'a> {
201 pub edges: &'a mut Vec<Edge>,
202 pub world: &'a CachedWorld,
203 pub mining_cache: &'a MiningCache,
204}