use azalea_client::{SprintDirection, WalkDirection};
use azalea_core::{direction::CardinalDirection, position::BlockPos};
use azalea_physics::collision::BlockWithShape;
use tracing::trace;
use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, MovesCtx};
use crate::pathfinder::{astar, costs::*, player_pos_to_block_pos, positions::RelBlockPos};
pub fn parkour_move(ctx: &mut MovesCtx, node: RelBlockPos) {
if !ctx.world.is_block_solid(node.down(1)) {
return;
}
parkour_forward_1_move(ctx, node);
parkour_forward_2_move(ctx, node);
parkour_forward_3_move(ctx, node);
}
fn parkour_forward_1_move(ctx: &mut MovesCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let gap_offset = RelBlockPos::new(dir.x(), 0, dir.z());
let offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
if ctx.world.is_block_standable((pos + gap_offset).down(1)) {
continue;
}
if !ctx.world.is_passable(pos + gap_offset) {
continue;
}
let ascend: i32 = if ctx.world.is_standable(pos + offset.up(1)) {
1
} else if ctx.world.is_standable(pos + offset) {
0
} else {
continue;
};
if !ctx.world.is_block_passable((pos + gap_offset).up(2)) {
continue;
}
if !ctx.world.is_block_passable(pos.up(2)) {
continue;
}
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue;
}
let cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 2.;
ctx.edges.push(Edge {
movement: astar::Movement {
target: pos + offset.up(ascend),
data: MoveData {
execute: &execute_parkour_move,
is_reached: &parkour_is_reached,
},
},
cost,
})
}
}
fn parkour_forward_2_move(ctx: &mut MovesCtx, pos: RelBlockPos) {
'dir: for dir in CardinalDirection::iter() {
let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
let offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
if ctx.world.is_block_standable((pos + gap_1_offset).down(1))
|| ctx.world.is_block_standable((pos + gap_2_offset).down(1))
{
continue;
}
let mut cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 3.;
let ascend: i32 = if ctx.world.is_standable(pos + offset.up(1)) {
1
} else if ctx.world.is_standable(pos + offset) {
0
} else if ctx.world.is_standable(pos + offset.down(1)) {
cost += FALL_N_BLOCKS_COST[2] / 2.;
-1
} else {
continue;
};
for offset in [gap_1_offset, gap_2_offset] {
if !ctx.world.is_passable(pos + offset) {
continue 'dir;
}
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue 'dir;
}
}
if !ctx.world.is_block_passable(pos.up(2)) {
continue;
}
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue;
}
ctx.edges.push(Edge {
movement: astar::Movement {
target: pos + offset.up(ascend),
data: MoveData {
execute: &execute_parkour_move,
is_reached: &parkour_is_reached,
},
},
cost,
})
}
}
fn parkour_forward_3_move(ctx: &mut MovesCtx, pos: RelBlockPos) {
'dir: for dir in CardinalDirection::iter() {
let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
let gap_3_offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
let offset = RelBlockPos::new(dir.x() * 4, 0, dir.z() * 4);
if ctx.world.is_block_standable((pos + gap_1_offset).down(1))
|| ctx.world.is_block_standable((pos + gap_2_offset).down(1))
|| ctx.world.is_block_standable((pos + gap_3_offset).down(1))
{
continue;
}
if !ctx.world.is_standable(pos + offset) {
continue;
};
for offset in [gap_1_offset, gap_2_offset, gap_3_offset] {
if !ctx.world.is_passable(pos + offset) {
continue 'dir;
}
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue 'dir;
}
}
if !ctx.world.is_block_passable(pos.up(2)) {
continue;
}
if !ctx.world.is_block_passable((pos + offset).up(2)) {
continue;
}
let cost = JUMP_PENALTY + SPRINT_ONE_BLOCK_COST * 4.;
ctx.edges.push(Edge {
movement: astar::Movement {
target: pos + offset,
data: MoveData {
execute: &execute_parkour_move,
is_reached: &parkour_is_reached,
},
},
cost,
})
}
}
fn execute_parkour_move(mut ctx: ExecuteCtx) {
let ExecuteCtx {
position,
target,
start,
..
} = ctx;
let start_center = start.center();
let target_center = target.center();
let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
let ascend: i32 = target.y - start.y;
if jump_distance >= 4 || (ascend > 0 && jump_distance >= 3) {
ctx.sprint(SprintDirection::Forward);
} else {
ctx.walk(WalkDirection::Forward);
}
let x_dir = (target.x - start.x).clamp(-1, 1);
let z_dir = (target.z - start.z).clamp(-1, 1);
let dir = BlockPos::new(x_dir, 0, z_dir);
let jump_at_pos = start + dir;
let is_at_start_block = player_pos_to_block_pos(position) == start;
let is_at_jump_block = player_pos_to_block_pos(position) == jump_at_pos;
let required_distance_from_center = if jump_distance <= 2 {
0.0
} else {
0.6
};
let distance_from_start = f64::max(
(position.x - start_center.x).abs(),
(position.z - start_center.z).abs(),
);
if !is_at_start_block
&& !is_at_jump_block
&& (position.y - start.y as f64) < 0.094
&& distance_from_start < 0.85
{
ctx.look_at(start_center);
trace!("looking at start_center");
} else {
ctx.look_at(target_center);
trace!("looking at target_center");
if !ctx.physics.on_ground() && ctx.physics.velocity.y.abs() < 0.1 {
let should_sneak = {
let world = ctx.world.read();
let pos_above = ctx.position.up(1.8 + 0.1);
let block_pos_above = BlockPos::from(pos_above);
let block_pos_above_plus_velocity =
BlockPos::from(pos_above + ctx.physics.velocity.with_y(0.) * 4.);
let block_above = world.get_block_state(block_pos_above).unwrap_or_default();
let block_above_plus_velocity = world
.get_block_state(block_pos_above_plus_velocity)
.unwrap_or_default();
!block_above.is_collision_shape_full()
&& !block_above_plus_velocity.is_collision_shape_empty()
};
if should_sneak {
ctx.sneak();
}
}
}
if !is_at_start_block && is_at_jump_block && distance_from_start > required_distance_from_center
{
ctx.jump();
}
}
#[must_use]
pub fn parkour_is_reached(
IsReachedCtx {
position,
target,
physics,
..
}: IsReachedCtx,
) -> bool {
if player_pos_to_block_pos(position) == target && (position.y - target.y as f64) < 0.094 {
return true;
}
player_pos_to_block_pos(position) == target && physics.on_ground()
}