1use std::f32::consts::SQRT_2;
2
3use azalea_block::{BlockState, properties};
4use azalea_client::{SprintDirection, WalkDirection};
5use azalea_core::{
6 direction::CardinalDirection,
7 position::{BlockPos, Vec3},
8};
9
10use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx, default_is_reached};
11use crate::pathfinder::{astar, costs::*, player_pos_to_block_pos, rel_block_pos::RelBlockPos};
12
13pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
14 forward_move(ctx, node);
15 ascend_move(ctx, node);
16 descend_move(ctx, node);
17 diagonal_move(ctx, node);
18 descend_forward_1_move(ctx, node);
19 downward_move(ctx, node);
20}
21
22fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
23 let mut base_cost = SPRINT_ONE_BLOCK_COST;
24 if ctx.world.is_block_water(pos.down(1)) {
27 base_cost = WALK_ONE_IN_WATER_COST;
28 }
29
30 for dir in CardinalDirection::iter() {
31 let offset = RelBlockPos::new(dir.x(), 0, dir.z());
32
33 let mut cost = base_cost;
34
35 let break_cost = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
36 if break_cost == f32::INFINITY {
37 continue;
38 }
39 cost += break_cost;
40
41 ctx.edges.push(Edge {
42 movement: astar::Movement {
43 target: pos + offset,
44 data: MoveData {
45 execute: &execute_forward_move,
46 is_reached: &default_is_reached,
47 },
48 },
49 cost,
50 })
51 }
52}
53
54fn execute_forward_move(mut ctx: ExecuteCtx) {
55 let center = ctx.target.center();
56 ctx.jump_if_in_water();
57
58 if ctx.mine_while_at_start(ctx.target.up(1)) {
59 return;
60 }
61 if ctx.mine_while_at_start(ctx.target) {
62 return;
63 }
64
65 ctx.look_at(center);
66 ctx.sprint(SprintDirection::Forward);
67}
68
69fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
70 let is_unusual_shape = !ctx.world.is_block_solid(pos.down(1));
74 let mut stair_facing = None;
75
76 if is_unusual_shape {
77 let block_below = ctx.world.get_block_state(pos.down(1));
80
81 let Some(found_stair_facing) = validate_stair_and_get_facing(block_below) else {
82 return;
85 };
86
87 stair_facing = Some(found_stair_facing);
88 }
89
90 for dir in CardinalDirection::iter() {
91 if let Some(stair_facing) = stair_facing {
92 let expected_stair_facing = cardinal_direction_to_facing_property(dir);
93 if stair_facing != expected_stair_facing {
94 continue;
95 }
96 }
97
98 let offset = RelBlockPos::new(dir.x(), 1, dir.z());
99
100 let break_cost_1 = ctx
101 .world
102 .cost_for_breaking_block(pos.up(2), ctx.mining_cache);
103 if break_cost_1 == f32::INFINITY {
104 continue;
105 }
106 let break_cost_2 = ctx.world.cost_for_standing(pos + offset, ctx.mining_cache);
107 if break_cost_2 == f32::INFINITY {
108 continue;
109 }
110
111 let cost = SPRINT_ONE_BLOCK_COST
112 + JUMP_PENALTY
113 + *JUMP_ONE_BLOCK_COST
114 + break_cost_1
115 + break_cost_2;
116
117 ctx.edges.push(Edge {
118 movement: astar::Movement {
119 target: pos + offset,
120 data: MoveData {
121 execute: &execute_ascend_move,
122 is_reached: &ascend_is_reached,
123 },
124 },
125 cost,
126 })
127 }
128}
129fn execute_ascend_move(mut ctx: ExecuteCtx) {
130 let ExecuteCtx {
131 target,
132 start,
133 position,
134 physics,
135 ..
136 } = ctx;
137
138 ctx.jump_if_in_water();
139
140 if ctx.mine_while_at_start(start.up(2)) {
141 return;
142 }
143 if ctx.mine_while_at_start(target) {
144 return;
145 }
146 if ctx.mine_while_at_start(target.up(1)) {
147 return;
148 }
149
150 let target_center = target.center();
151
152 ctx.look_at(target_center);
153 ctx.walk(WalkDirection::Forward);
154
155 let x_axis = target.x - start.x; let z_axis = target.z - start.z; let x_axis_abs = x_axis.abs(); let z_axis_abs = z_axis.abs(); let flat_distance_to_next = x_axis_abs as f64 * (target_center.x - position.x)
165 + z_axis_abs as f64 * (target_center.z - position.z);
166 let side_distance = z_axis_abs as f64 * (target_center.x - position.x).abs()
167 + x_axis_abs as f64 * (target_center.z - position.z).abs();
168
169 let lateral_motion =
170 x_axis_abs as f64 * physics.velocity.z + z_axis_abs as f64 * physics.velocity.x;
171 if lateral_motion.abs() > 0.1 {
172 return;
173 }
174
175 if flat_distance_to_next > 1.2 || side_distance > 0.2 {
176 return;
177 }
178
179 let block_below_target = ctx.get_block_state(target.down(1));
182 if let Some(stair_facing) = validate_stair_and_get_facing(block_below_target) {
183 let expected_stair_facing = match (x_axis, z_axis) {
184 (0, 1) => Some(properties::FacingCardinal::North),
185 (1, 0) => Some(properties::FacingCardinal::East),
186 (0, -1) => Some(properties::FacingCardinal::South),
187 (-1, 0) => Some(properties::FacingCardinal::West),
188 _ => None,
189 };
190 if let Some(expected_stair_facing) = expected_stair_facing
191 && stair_facing == expected_stair_facing
192 {
193 return;
194 }
195 }
196
197 if player_pos_to_block_pos(position) == start {
198 if target.y as f64 - position.y > 0.5 {
200 ctx.jump();
201 }
202 }
203}
204#[must_use]
205pub fn ascend_is_reached(
206 IsReachedCtx {
207 position, target, ..
208 }: IsReachedCtx,
209) -> bool {
210 BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
211}
212
213fn validate_stair_and_get_facing(block_state: BlockState) -> Option<properties::FacingCardinal> {
214 let top_bottom = block_state.property::<properties::TopBottom>();
215 if top_bottom != Some(properties::TopBottom::Bottom) {
216 return None;
217 }
218
219 block_state.property::<properties::FacingCardinal>()
220}
221fn cardinal_direction_to_facing_property(dir: CardinalDirection) -> properties::FacingCardinal {
222 match dir {
223 CardinalDirection::North => properties::FacingCardinal::North,
224 CardinalDirection::East => properties::FacingCardinal::East,
225 CardinalDirection::South => properties::FacingCardinal::South,
226 CardinalDirection::West => properties::FacingCardinal::West,
227 }
228}
229
230fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
231 for dir in CardinalDirection::iter() {
232 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
233 let new_horizontal_position = pos + dir_delta;
234
235 let break_cost_1 = ctx
236 .world
237 .cost_for_passing(new_horizontal_position, ctx.mining_cache);
238 if break_cost_1 == f32::INFINITY {
239 continue;
240 }
241
242 let mut fall_distance = ctx.world.fall_distance(new_horizontal_position);
243 if fall_distance > 3 {
244 continue;
245 }
246
247 if fall_distance == 0 {
248 fall_distance = 1
250 }
251
252 let new_position = new_horizontal_position.down(fall_distance as i32);
253
254 let break_cost_2;
256 if fall_distance == 1 {
257 break_cost_2 = ctx.world.cost_for_standing(new_position, ctx.mining_cache);
258 if break_cost_2 == f32::INFINITY {
259 continue;
260 }
261 } else {
262 if !ctx.world.is_standable(new_position) {
264 continue;
265 }
266 break_cost_2 = 0.;
267 }
268
269 let cost = WALK_OFF_BLOCK_COST
270 + f32::max(
271 FALL_N_BLOCKS_COST
272 .get(fall_distance as usize)
273 .copied()
274 .unwrap_or(f32::INFINITY),
277 CENTER_AFTER_FALL_COST,
278 )
279 + break_cost_1
280 + break_cost_2;
281
282 ctx.edges.push(Edge {
283 movement: astar::Movement {
284 target: new_position,
285 data: MoveData {
286 execute: &execute_descend_move,
287 is_reached: &descend_is_reached,
288 },
289 },
290 cost,
291 })
292 }
293}
294fn execute_descend_move(mut ctx: ExecuteCtx) {
295 let ExecuteCtx {
296 target,
297 start,
298 position,
299 ..
300 } = ctx;
301
302 for i in (0..=(start.y - target.y + 1)).rev() {
303 if ctx.mine_while_at_start(target.up(i)) {
304 return;
305 }
306 }
307
308 let start_center = start.center();
309 let center = target.center();
310
311 let horizontal_distance_from_target = (center - position).horizontal_distance_squared().sqrt();
312 let horizontal_distance_from_start = (start.center() - position)
313 .horizontal_distance_squared()
314 .sqrt();
315
316 let dest_ahead = Vec3::new(
317 start_center.x + (center.x - start_center.x) * 1.5,
318 center.y,
319 start_center.z + (center.z - start_center.z) * 1.5,
320 );
321
322 if (BlockPos::from(position).horizontal_distance_squared_to(target) > 0)
323 || horizontal_distance_from_target > 0.25
324 {
325 if horizontal_distance_from_start < 1.25 {
326 ctx.look_at(dest_ahead);
328 ctx.walk(WalkDirection::Forward);
329 } else {
330 ctx.look_at(center);
331 ctx.walk(WalkDirection::Forward);
332 }
333 } else {
334 ctx.walk(WalkDirection::None);
335 }
336}
337#[must_use]
338pub fn descend_is_reached(
339 IsReachedCtx {
340 target,
341 start,
342 position,
343 physics,
344 ..
345 }: IsReachedCtx,
346) -> bool {
347 let dest_ahead = BlockPos::new(
348 start.x + (target.x - start.x) * 2,
349 target.y,
350 start.z + (target.z - start.z) * 2,
351 );
352
353 if player_pos_to_block_pos(position) == target
354 || player_pos_to_block_pos(position) == dest_ahead
355 {
356 if (position.y - target.y as f64) < 0.5 {
357 return true;
358 }
359 } else if player_pos_to_block_pos(position).up(1) == target && physics.on_ground() {
360 return true;
361 }
362 false
363}
364
365fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
366 for dir in CardinalDirection::iter() {
367 let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
368 let gap_horizontal_position = pos + dir_delta;
369 let new_horizontal_position = pos + dir_delta * 2;
370
371 let gap_fall_distance = ctx.world.fall_distance(gap_horizontal_position);
372 let fall_distance = ctx.world.fall_distance(new_horizontal_position);
373
374 if fall_distance == 0 || fall_distance > 3 || gap_fall_distance < fall_distance {
375 continue;
376 }
377
378 let new_position = new_horizontal_position.down(fall_distance as i32);
379
380 if !ctx.world.is_passable(new_horizontal_position) {
382 continue;
383 }
384 if !ctx.world.is_passable(gap_horizontal_position) {
385 continue;
386 }
387 if !ctx.world.is_standable(new_position) {
389 continue;
390 }
391
392 let cost = WALK_OFF_BLOCK_COST
393 + WALK_ONE_BLOCK_COST
394 + f32::max(
395 FALL_N_BLOCKS_COST
396 .get(fall_distance as usize)
397 .copied()
398 .unwrap_or(f32::INFINITY),
401 CENTER_AFTER_FALL_COST,
402 );
403
404 ctx.edges.push(Edge {
405 movement: astar::Movement {
406 target: new_position,
407 data: MoveData {
408 execute: &execute_descend_move,
409 is_reached: &descend_is_reached,
410 },
411 },
412 cost,
413 })
414 }
415}
416
417fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
418 let mut base_cost = SPRINT_ONE_BLOCK_COST;
419 if ctx.world.is_block_water(pos.down(1)) {
420 base_cost = WALK_ONE_IN_WATER_COST;
421 }
422
423 base_cost = base_cost.mul_add(SQRT_2, 0.001);
425
426 for dir in CardinalDirection::iter() {
427 let right = dir.right();
428 let offset = RelBlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
429 let left_pos = RelBlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
430 let right_pos = RelBlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
431
432 let mut cost = base_cost;
433
434 let left_passable = ctx.world.is_passable(left_pos);
435 let right_passable = ctx.world.is_passable(right_pos);
436
437 if !left_passable && !right_passable {
438 continue;
439 }
440
441 if !left_passable || !right_passable {
442 cost += WALK_ONE_BLOCK_COST / 2.;
444 }
445
446 if !ctx.world.is_standable(pos + offset) {
447 continue;
448 }
449
450 ctx.edges.push(Edge {
451 movement: astar::Movement {
452 target: pos + offset,
453 data: MoveData {
454 execute: &execute_diagonal_move,
455 is_reached: &default_is_reached,
456 },
457 },
458 cost,
459 })
460 }
461}
462fn execute_diagonal_move(mut ctx: ExecuteCtx) {
463 let target_center = ctx.target.center();
464
465 ctx.jump_if_in_water();
466
467 ctx.look_at(target_center);
468 ctx.sprint(SprintDirection::Forward);
469}
470
471fn downward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
473 if !ctx.world.is_block_solid(pos.down(2)) {
475 return;
476 }
477
478 let break_cost = ctx
479 .world
480 .cost_for_breaking_block(pos.down(1), ctx.mining_cache);
481 if break_cost == f32::INFINITY {
482 return;
483 }
484
485 let cost = FALL_N_BLOCKS_COST[1] + break_cost;
486
487 ctx.edges.push(Edge {
488 movement: astar::Movement {
489 target: pos.down(1),
490 data: MoveData {
491 execute: &execute_downward_move,
492 is_reached: &default_is_reached,
493 },
494 },
495 cost,
496 })
497}
498fn execute_downward_move(mut ctx: ExecuteCtx) {
499 let ExecuteCtx {
500 target, position, ..
501 } = ctx;
502
503 let target_center = target.center();
504
505 let horizontal_distance_from_target = (target_center - position)
506 .horizontal_distance_squared()
507 .sqrt();
508
509 if horizontal_distance_from_target > 0.25 {
510 ctx.look_at(target_center);
511 ctx.walk(WalkDirection::Forward);
512 } else if ctx.mine_while_at_start(target) {
513 ctx.walk(WalkDirection::None);
514 } else if BlockPos::from(position) != target {
515 ctx.look_at(target_center);
516 ctx.walk(WalkDirection::Forward);
517 } else {
518 ctx.walk(WalkDirection::None);
519 }
520}