1use std::f32::consts::SQRT_2;
2
3use azalea_client::{SprintDirection, WalkDirection};
4use azalea_core::{
5 direction::CardinalDirection,
6 position::{BlockPos, Vec3},
7};
8
9use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx, default_is_reached};
10use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
11
12pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
13 forward_move(ctx, node);
14 ascend_move(ctx, node);
15 descend_move(ctx, node);
16 diagonal_move(ctx, node);
17 descend_forward_1_move(ctx, node);
18 downward_move(ctx, node);
19}
20
21fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
22 for dir in CardinalDirection::iter() {
23 let offset = RelBlockPos::new(dir.x(), 0, dir.z());
24
25 let mut cost = SPRINT_ONE_BLOCK_COST;
26
27 let break_cost = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
28 if break_cost == f32::INFINITY {
29 continue;
30 }
31 cost += break_cost;
32
33 ctx.edges.push(Edge {
34 movement: astar::Movement {
35 target: pos + offset,
36 data: MoveData {
37 execute: &execute_forward_move,
38 is_reached: &default_is_reached,
39 },
40 },
41 cost,
42 })
43 }
44}
45
46fn execute_forward_move(mut ctx: ExecuteCtx) {
47 let center = ctx.target.center();
48
49 if ctx.mine_while_at_start(ctx.target.up(1)) {
50 return;
51 }
52 if ctx.mine_while_at_start(ctx.target) {
53 return;
54 }
55
56 ctx.look_at(center);
57 ctx.sprint(SprintDirection::Forward);
58}
59
60fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
61 for dir in CardinalDirection::iter() {
62 let offset = RelBlockPos::new(dir.x(), 1, dir.z());
63
64 let break_cost_1 = ctx
65 .world
66 .cost_for_breaking_block(pos.up(2), ctx.mining_cache);
67 if break_cost_1 == f32::INFINITY {
68 continue;
69 }
70 let break_cost_2 = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
71 if break_cost_2 == f32::INFINITY {
72 continue;
73 }
74
75 let cost = SPRINT_ONE_BLOCK_COST
76 + JUMP_PENALTY
77 + *JUMP_ONE_BLOCK_COST
78 + break_cost_1
79 + break_cost_2;
80
81 ctx.edges.push(Edge {
82 movement: astar::Movement {
83 target: pos + offset,
84 data: MoveData {
85 execute: &execute_ascend_move,
86 is_reached: &ascend_is_reached,
87 },
88 },
89 cost,
90 })
91 }
92}
93fn execute_ascend_move(mut ctx: ExecuteCtx) {
94 let ExecuteCtx {
95 target,
96 start,
97 position,
98 physics,
99 ..
100 } = ctx;
101
102 if ctx.mine_while_at_start(start.up(2)) {
103 return;
104 }
105 if ctx.mine_while_at_start(target) {
106 return;
107 }
108 if ctx.mine_while_at_start(target.up(1)) {
109 return;
110 }
111
112 let target_center = target.center();
113
114 ctx.look_at(target_center);
115 ctx.walk(WalkDirection::Forward);
116
117 let x_axis = (start.x - target.x).abs(); let z_axis = (start.z - target.z).abs(); let flat_distance_to_next = x_axis as f64 * (target_center.x - position.x)
124 + z_axis as f64 * (target_center.z - position.z);
125 let side_distance = z_axis as f64 * (target_center.x - position.x).abs()
126 + x_axis as f64 * (target_center.z - position.z).abs();
127
128 let lateral_motion = x_axis as f64 * physics.velocity.z + z_axis as f64 * physics.velocity.x;
129 if lateral_motion.abs() > 0.1 {
130 return;
131 }
132
133 if flat_distance_to_next > 1.2 || side_distance > 0.2 {
134 return;
135 }
136
137 if BlockPos::from(position) == start {
138 ctx.jump();
139 }
140}
141#[must_use]
142pub fn ascend_is_reached(
143 IsReachedCtx {
144 position, target, ..
145 }: IsReachedCtx,
146) -> bool {
147 BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
148}
149
150fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
151 for dir in CardinalDirection::iter() {
152 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
153 let new_horizontal_position = pos + dir_delta;
154
155 let break_cost_1 = ctx
156 .world
157 .cost_for_passing(new_horizontal_position, ctx.mining_cache);
158 if break_cost_1 == f32::INFINITY {
159 continue;
160 }
161
162 let mut fall_distance = ctx.world.fall_distance(new_horizontal_position);
163 if fall_distance > 3 {
164 continue;
165 }
166
167 if fall_distance == 0 {
168 fall_distance = 1
170 }
171
172 let new_position = new_horizontal_position.down(fall_distance as i32);
173
174 let break_cost_2;
176 if fall_distance == 1 {
177 break_cost_2 = ctx.world.cost_for_standing(new_position, ctx.mining_cache);
178 if break_cost_2 == f32::INFINITY {
179 continue;
180 }
181 } else {
182 if !ctx.world.is_standable(new_position) {
184 continue;
185 }
186 break_cost_2 = 0.;
187 }
188
189 let cost = WALK_OFF_BLOCK_COST
190 + f32::max(
191 FALL_N_BLOCKS_COST
192 .get(fall_distance as usize)
193 .copied()
194 .unwrap_or(f32::INFINITY),
197 CENTER_AFTER_FALL_COST,
198 )
199 + break_cost_1
200 + break_cost_2;
201
202 ctx.edges.push(Edge {
203 movement: astar::Movement {
204 target: new_position,
205 data: MoveData {
206 execute: &execute_descend_move,
207 is_reached: &descend_is_reached,
208 },
209 },
210 cost,
211 })
212 }
213}
214fn execute_descend_move(mut ctx: ExecuteCtx) {
215 let ExecuteCtx {
216 target,
217 start,
218 position,
219 ..
220 } = ctx;
221
222 for i in (0..=(start.y - target.y + 1)).rev() {
223 if ctx.mine_while_at_start(target.up(i)) {
224 return;
225 }
226 }
227
228 let start_center = start.center();
229 let center = target.center();
230
231 let horizontal_distance_from_target = (center - position).horizontal_distance_squared().sqrt();
232 let horizontal_distance_from_start = (start.center() - position)
233 .horizontal_distance_squared()
234 .sqrt();
235
236 let dest_ahead = Vec3::new(
237 start_center.x + (center.x - start_center.x) * 1.5,
238 center.y,
239 start_center.z + (center.z - start_center.z) * 1.5,
240 );
241
242 if BlockPos::from(position) != target || horizontal_distance_from_target > 0.25 {
243 if horizontal_distance_from_start < 1.25 {
244 ctx.look_at(dest_ahead);
246 ctx.walk(WalkDirection::Forward);
247 } else {
248 ctx.look_at(center);
249 ctx.walk(WalkDirection::Forward);
250 }
251 } else {
252 ctx.walk(WalkDirection::None);
253 }
254}
255#[must_use]
256pub fn descend_is_reached(
257 IsReachedCtx {
258 target,
259 start,
260 position,
261 ..
262 }: IsReachedCtx,
263) -> bool {
264 let dest_ahead = BlockPos::new(
265 start.x + (target.x - start.x) * 2,
266 target.y,
267 start.z + (target.z - start.z) * 2,
268 );
269
270 (BlockPos::from(position) == target || BlockPos::from(position) == dest_ahead)
271 && (position.y - target.y as f64) < 0.5
272}
273
274fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
275 for dir in CardinalDirection::iter() {
276 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
277 let gap_horizontal_position = pos + dir_delta;
278 let new_horizontal_position = pos + dir_delta * 2;
279
280 let gap_fall_distance = ctx.world.fall_distance(gap_horizontal_position);
281 let fall_distance = ctx.world.fall_distance(new_horizontal_position);
282
283 if fall_distance == 0 || fall_distance > 3 || gap_fall_distance < fall_distance {
284 continue;
285 }
286
287 let new_position = new_horizontal_position.down(fall_distance as i32);
288
289 if !ctx.world.is_passable(new_horizontal_position) {
291 continue;
292 }
293 if !ctx.world.is_passable(gap_horizontal_position) {
294 continue;
295 }
296 if !ctx.world.is_standable(new_position) {
298 continue;
299 }
300
301 let cost = WALK_OFF_BLOCK_COST
302 + WALK_ONE_BLOCK_COST
303 + f32::max(
304 FALL_N_BLOCKS_COST
305 .get(fall_distance as usize)
306 .copied()
307 .unwrap_or(f32::INFINITY),
310 CENTER_AFTER_FALL_COST,
311 );
312
313 ctx.edges.push(Edge {
314 movement: astar::Movement {
315 target: new_position,
316 data: MoveData {
317 execute: &execute_descend_move,
318 is_reached: &descend_is_reached,
319 },
320 },
321 cost,
322 })
323 }
324}
325
326fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
327 for dir in CardinalDirection::iter() {
328 let right = dir.right();
329 let offset = RelBlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
330 let left_pos = RelBlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
331 let right_pos = RelBlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
332
333 let mut cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
335
336 let left_passable = ctx.world.is_passable(left_pos);
337 let right_passable = ctx.world.is_passable(right_pos);
338
339 if !left_passable && !right_passable {
340 continue;
341 }
342
343 if !left_passable || !right_passable {
344 cost += WALK_ONE_BLOCK_COST / 2.;
346 }
347
348 if !ctx.world.is_standable(pos + offset) {
349 continue;
350 }
351
352 ctx.edges.push(Edge {
353 movement: astar::Movement {
354 target: pos + offset,
355 data: MoveData {
356 execute: &execute_diagonal_move,
357 is_reached: &default_is_reached,
358 },
359 },
360 cost,
361 })
362 }
363}
364fn execute_diagonal_move(mut ctx: ExecuteCtx) {
365 let target_center = ctx.target.center();
366
367 ctx.look_at(target_center);
368 ctx.sprint(SprintDirection::Forward);
369}
370
371fn downward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
373 if !ctx.world.is_block_solid(pos.down(2)) {
375 return;
376 }
377
378 let break_cost = ctx
379 .world
380 .cost_for_breaking_block(pos.down(1), ctx.mining_cache);
381 if break_cost == f32::INFINITY {
382 return;
383 }
384
385 let cost = FALL_N_BLOCKS_COST[1] + break_cost;
386
387 ctx.edges.push(Edge {
388 movement: astar::Movement {
389 target: pos.down(1),
390 data: MoveData {
391 execute: &execute_downward_move,
392 is_reached: &default_is_reached,
393 },
394 },
395 cost,
396 })
397}
398fn execute_downward_move(mut ctx: ExecuteCtx) {
399 let ExecuteCtx {
400 target, position, ..
401 } = ctx;
402
403 let target_center = target.center();
404
405 let horizontal_distance_from_target = (target_center - position)
406 .horizontal_distance_squared()
407 .sqrt();
408
409 if horizontal_distance_from_target > 0.25 {
410 ctx.look_at(target_center);
411 ctx.walk(WalkDirection::Forward);
412 } else if ctx.mine_while_at_start(target) {
413 ctx.walk(WalkDirection::None);
414 } else if BlockPos::from(position) != target {
415 ctx.look_at(target_center);
416 ctx.walk(WalkDirection::Forward);
417 } else {
418 ctx.walk(WalkDirection::None);
419 }
420}