azalea/pathfinder/moves/
parkour.rs

1use azalea_client::{SprintDirection, WalkDirection};
2use azalea_core::{direction::CardinalDirection, position::BlockPos};
3use tracing::trace;
4
5use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
6use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
7
8pub fn parkour_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
9    parkour_forward_1_move(ctx, node);
10    parkour_forward_2_move(ctx, node);
11    parkour_forward_3_move(ctx, node);
12}
13
14fn parkour_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
15    for dir in CardinalDirection::iter() {
16        let gap_offset = RelBlockPos::new(dir.x(), 0, dir.z());
17        let offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
18
19        // make sure we actually have to jump
20        if ctx.world.is_block_solid((pos + gap_offset).down(1)) {
21            continue;
22        }
23        if !ctx.world.is_passable(pos + gap_offset) {
24            continue;
25        }
26
27        let ascend: i32 = if ctx.world.is_standable(pos + offset.up(1)) {
28            // ascend
29            1
30        } else if ctx.world.is_standable(pos + offset) {
31            // forward
32            0
33        } else {
34            continue;
35        };
36
37        // make sure we have space to jump
38        if !ctx.world.is_block_passable((pos + gap_offset).up(2)) {
39            continue;
40        }
41
42        // make sure there's not a block above us
43        if !ctx.world.is_block_passable(pos.up(2)) {
44            continue;
45        }
46        // make sure there's not a block above the target
47        if !ctx.world.is_block_passable((pos + offset).up(2)) {
48            continue;
49        }
50
51        let cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 2. + CENTER_AFTER_FALL_COST;
52
53        ctx.edges.push(Edge {
54            movement: astar::Movement {
55                target: pos + offset.up(ascend),
56                data: MoveData {
57                    execute: &execute_parkour_move,
58                    is_reached: &parkour_is_reached,
59                },
60            },
61            cost,
62        })
63    }
64}
65
66fn parkour_forward_2_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
67    'dir: for dir in CardinalDirection::iter() {
68        let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
69        let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
70        let offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
71
72        // make sure we actually have to jump
73        if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
74            || ctx.world.is_block_solid((pos + gap_2_offset).down(1))
75        {
76            continue;
77        }
78
79        let ascend: i32 = if ctx.world.is_standable(pos + offset.up(1)) {
80            1
81        } else if ctx.world.is_standable(pos + offset) {
82            0
83        } else {
84            continue;
85        };
86
87        // make sure we have space to jump
88        for offset in [gap_1_offset, gap_2_offset] {
89            if !ctx.world.is_passable(pos + offset) {
90                continue 'dir;
91            }
92            if !ctx.world.is_block_passable((pos + offset).up(2)) {
93                continue 'dir;
94            }
95        }
96        // make sure there's not a block above us
97        if !ctx.world.is_block_passable(pos.up(2)) {
98            continue;
99        }
100        // make sure there's not a block above the target
101        if !ctx.world.is_block_passable((pos + offset).up(2)) {
102            continue;
103        }
104
105        let cost = JUMP_PENALTY + WALK_ONE_BLOCK_COST * 3. + CENTER_AFTER_FALL_COST;
106
107        ctx.edges.push(Edge {
108            movement: astar::Movement {
109                target: pos + offset.up(ascend),
110                data: MoveData {
111                    execute: &execute_parkour_move,
112                    is_reached: &parkour_is_reached,
113                },
114            },
115            cost,
116        })
117    }
118}
119
120fn parkour_forward_3_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
121    'dir: for dir in CardinalDirection::iter() {
122        let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
123        let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
124        let gap_3_offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
125        let offset = RelBlockPos::new(dir.x() * 4, 0, dir.z() * 4);
126
127        // make sure we actually have to jump
128        if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
129            || ctx.world.is_block_solid((pos + gap_2_offset).down(1))
130            || ctx.world.is_block_solid((pos + gap_3_offset).down(1))
131        {
132            continue;
133        }
134
135        if !ctx.world.is_standable(pos + offset) {
136            continue;
137        };
138
139        // make sure we have space to jump
140        for offset in [gap_1_offset, gap_2_offset, gap_3_offset] {
141            if !ctx.world.is_passable(pos + offset) {
142                continue 'dir;
143            }
144            if !ctx.world.is_block_passable((pos + offset).up(2)) {
145                continue 'dir;
146            }
147        }
148        // make sure there's not a block above us
149        if !ctx.world.is_block_passable(pos.up(2)) {
150            continue;
151        }
152        // make sure there's not a block above the target
153        if !ctx.world.is_block_passable((pos + offset).up(2)) {
154            continue;
155        }
156
157        let cost = JUMP_PENALTY + SPRINT_ONE_BLOCK_COST * 4. + CENTER_AFTER_FALL_COST;
158
159        ctx.edges.push(Edge {
160            movement: astar::Movement {
161                target: pos + offset,
162                data: MoveData {
163                    execute: &execute_parkour_move,
164                    is_reached: &parkour_is_reached,
165                },
166            },
167            cost,
168        })
169    }
170}
171
172fn execute_parkour_move(mut ctx: ExecuteCtx) {
173    let ExecuteCtx {
174        position,
175        target,
176        start,
177        ..
178    } = ctx;
179
180    let start_center = start.center();
181    let target_center = target.center();
182
183    let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
184
185    let ascend: i32 = target.y - start.y;
186
187    if jump_distance >= 4 || (ascend > 0 && jump_distance >= 3) {
188        // 3 block gap OR 2 block gap with ascend
189        ctx.sprint(SprintDirection::Forward);
190    } else {
191        ctx.walk(WalkDirection::Forward);
192    }
193
194    let x_dir = (target.x - start.x).clamp(-1, 1);
195    let z_dir = (target.z - start.z).clamp(-1, 1);
196    let dir = BlockPos::new(x_dir, 0, z_dir);
197    let jump_at_pos = start + dir;
198
199    let is_at_start_block = BlockPos::from(position) == start;
200    let is_at_jump_block = BlockPos::from(position) == jump_at_pos;
201
202    let required_distance_from_center = if jump_distance <= 2 {
203        // 1 block gap
204        0.0
205    } else {
206        0.6
207    };
208    let distance_from_start = f64::max(
209        (position.x - start_center.x).abs(),
210        (position.z - start_center.z).abs(),
211    );
212
213    if !is_at_start_block
214        && !is_at_jump_block
215        && (position.y - start.y as f64) < 0.094
216        && distance_from_start < 0.85
217    {
218        // we have to be on the start block to jump
219        ctx.look_at(start_center);
220        trace!("looking at start_center");
221    } else {
222        ctx.look_at(target_center);
223        trace!("looking at target_center");
224    }
225
226    if !is_at_start_block && is_at_jump_block && distance_from_start > required_distance_from_center
227    {
228        ctx.jump();
229    }
230}
231
232#[must_use]
233pub fn parkour_is_reached(
234    IsReachedCtx {
235        position, target, ..
236    }: IsReachedCtx,
237) -> bool {
238    // 0.094 and not 0 for lilypads
239    BlockPos::from(position) == target && (position.y - target.y as f64) < 0.094
240}