mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
start implementing parkour
This commit is contained in:
parent
1c97e23290
commit
b635846344
5 changed files with 257 additions and 13 deletions
|
@ -106,7 +106,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let best_path = determine_best_path(&best_paths, &heuristic);
|
let best_path = determine_best_path(&best_paths, &start);
|
||||||
|
|
||||||
Path {
|
Path {
|
||||||
movements: reconstruct_path(nodes, best_path),
|
movements: reconstruct_path(nodes, best_path),
|
||||||
|
@ -114,23 +114,20 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_DISTANCE_PATH: f32 = 5.;
|
fn determine_best_path<P>(best_paths: &[P; 7], start: &P) -> P
|
||||||
|
|
||||||
fn determine_best_path<P, HeuristicFn>(best_node: &[P; 7], heuristic: &HeuristicFn) -> P
|
|
||||||
where
|
where
|
||||||
HeuristicFn: Fn(P) -> f32,
|
|
||||||
P: Eq + Hash + Copy + Debug,
|
P: Eq + Hash + Copy + Debug,
|
||||||
{
|
{
|
||||||
// this basically makes sure we don't create a path that's really short
|
// this basically makes sure we don't create a path that's really short
|
||||||
|
|
||||||
for node in best_node.iter() {
|
for node in best_paths.iter() {
|
||||||
// square MIN_DISTANCE_PATH because we're comparing squared distances
|
if node != start {
|
||||||
if heuristic(*node) > MIN_DISTANCE_PATH * MIN_DISTANCE_PATH {
|
println!("chose best node {:?}", node);
|
||||||
return *node;
|
return *node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
warn!("No best node found, returning first node");
|
warn!("No best node found, returning first node");
|
||||||
best_node[0]
|
best_paths[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reconstruct_path<P, M>(mut nodes: HashMap<P, Node<P, M>>, current: P) -> Vec<Movement<P, M>>
|
fn reconstruct_path<P, M>(mut nodes: HashMap<P, Node<P, M>>, current: P) -> Vec<Movement<P, M>>
|
||||||
|
|
|
@ -86,7 +86,7 @@ pub struct GotoEvent {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub goal: Arc<dyn Goal + Send + Sync>,
|
pub goal: Arc<dyn Goal + Send + Sync>,
|
||||||
/// The function that's used for checking what moves are possible. Usually
|
/// The function that's used for checking what moves are possible. Usually
|
||||||
/// `pathfinder::moves::basic::basic_move`
|
/// `pathfinder::moves::default_move`
|
||||||
pub successors_fn: SuccessorsFn,
|
pub successors_fn: SuccessorsFn,
|
||||||
}
|
}
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
|
@ -124,7 +124,7 @@ impl PathfinderClientExt for azalea_client::Client {
|
||||||
self.ecs.lock().send_event(GotoEvent {
|
self.ecs.lock().send_event(GotoEvent {
|
||||||
entity: self.entity,
|
entity: self.entity,
|
||||||
goal: Arc::new(goal),
|
goal: Arc::new(goal),
|
||||||
successors_fn: moves::basic::basic_move,
|
successors_fn: moves::default_move,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,7 +370,12 @@ fn tick_execute_path(
|
||||||
position: **position,
|
position: **position,
|
||||||
physics,
|
physics,
|
||||||
};
|
};
|
||||||
if (movement.data.is_reached)(is_reached_ctx) {
|
let on_ground_if_last = if i == pathfinder.path.len() - 1 {
|
||||||
|
physics.on_ground
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if (movement.data.is_reached)(is_reached_ctx) && on_ground_if_last {
|
||||||
pathfinder.path = pathfinder.path.split_off(i + 1);
|
pathfinder.path = pathfinder.path.split_off(i + 1);
|
||||||
pathfinder.last_reached_node = Some(movement.target);
|
pathfinder.last_reached_node = Some(movement.target);
|
||||||
pathfinder.last_node_reached_at = Some(Instant::now());
|
pathfinder.last_node_reached_at = Some(Instant::now());
|
||||||
|
|
|
@ -268,7 +268,8 @@ fn diagonal_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
if !is_standable(&(pos + offset), world) {
|
if !is_standable(&(pos + offset), world) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let cost = SPRINT_ONE_BLOCK_COST * SQRT_2;
|
// +0.001 so it doesn't unnecessarily go diagonal sometimes
|
||||||
|
let cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
|
||||||
|
|
||||||
edges.push(Edge {
|
edges.push(Edge {
|
||||||
movement: astar::Movement {
|
movement: astar::Movement {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod basic;
|
pub mod basic;
|
||||||
|
pub mod parkour;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
@ -114,6 +115,13 @@ pub struct IsReachedCtx<'a> {
|
||||||
pub physics: &'a azalea_entity::Physics,
|
pub physics: &'a azalea_entity::Physics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_move(world: &Instance, node: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
edges.extend(basic::basic_move(world, node));
|
||||||
|
edges.extend(parkour::parkour_move(world, node));
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether the entity is at the node and should start going to the
|
/// Returns whether the entity is at the node and should start going to the
|
||||||
/// next node.
|
/// next node.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
233
azalea/src/pathfinder/moves/parkour.rs
Normal file
233
azalea/src/pathfinder/moves/parkour.rs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection};
|
||||||
|
use azalea_core::{BlockPos, CardinalDirection};
|
||||||
|
use azalea_world::Instance;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
pathfinder::{astar, costs::*},
|
||||||
|
JumpEvent, LookAtEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
default_is_reached, is_block_passable, is_block_solid, is_passable, is_standable, Edge,
|
||||||
|
ExecuteCtx, MoveData,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parkour_move(world: &Instance, node: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
edges.extend(parkour_forward_1_move(world, node));
|
||||||
|
edges.extend(parkour_headhitter_forward_1_move(world, node));
|
||||||
|
edges.extend(parkour_forward_2_move(world, node));
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parkour_forward_1_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let gap_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
|
||||||
|
let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
|
|
||||||
|
if !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_passable(&(pos + gap_offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_block_passable(&(pos + gap_offset).up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure we actually have to jump
|
||||||
|
if is_block_solid(&(pos + gap_offset).down(1), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure it's not a headhitter
|
||||||
|
if !is_block_passable(&pos.up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = *JUMP_ONE_BLOCK_COST + SPRINT_ONE_BLOCK_COST + SPRINT_ONE_BLOCK_COST;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_parkour_move,
|
||||||
|
is_reached: &default_is_reached,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parkour_forward_2_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let gap_1_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
|
||||||
|
let gap_2_offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
|
let offset = BlockPos::new(dir.x() * 3, 0, dir.z() * 3);
|
||||||
|
|
||||||
|
if !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_passable(&(pos + gap_1_offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_block_passable(&(pos + gap_1_offset).up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_passable(&(pos + gap_2_offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_block_passable(&(pos + gap_2_offset).up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure we actually have to jump
|
||||||
|
if is_block_solid(&(pos + gap_1_offset).down(1), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure it's not a headhitter
|
||||||
|
if !is_block_passable(&pos.up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = *JUMP_ONE_BLOCK_COST
|
||||||
|
+ SPRINT_ONE_BLOCK_COST
|
||||||
|
+ SPRINT_ONE_BLOCK_COST
|
||||||
|
+ SPRINT_ONE_BLOCK_COST;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_parkour_move,
|
||||||
|
is_reached: &default_is_reached,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parkour_headhitter_forward_1_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let gap_offset = BlockPos::new(dir.x() * 1, 0, dir.z() * 1);
|
||||||
|
let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
|
|
||||||
|
if !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_passable(&(pos + gap_offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_block_passable(&(pos + gap_offset).up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure we actually have to jump
|
||||||
|
if is_block_solid(&(pos + gap_offset).down(1), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make sure it is a headhitter
|
||||||
|
if !is_block_solid(&pos.up(2), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = *JUMP_ONE_BLOCK_COST + WALK_ONE_BLOCK_COST + WALK_ONE_BLOCK_COST;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_headhitter_parkour_move,
|
||||||
|
is_reached: &default_is_reached,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_parkour_move(
|
||||||
|
ExecuteCtx {
|
||||||
|
entity,
|
||||||
|
target,
|
||||||
|
start,
|
||||||
|
look_at_events,
|
||||||
|
sprint_events,
|
||||||
|
walk_events,
|
||||||
|
jump_events,
|
||||||
|
..
|
||||||
|
}: ExecuteCtx,
|
||||||
|
) {
|
||||||
|
let center = target.center();
|
||||||
|
look_at_events.send(LookAtEvent {
|
||||||
|
entity,
|
||||||
|
position: center,
|
||||||
|
});
|
||||||
|
|
||||||
|
let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
|
||||||
|
|
||||||
|
if jump_distance > 2 {
|
||||||
|
sprint_events.send(StartSprintEvent {
|
||||||
|
entity,
|
||||||
|
direction: SprintDirection::Forward,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
walk_events.send(StartWalkEvent {
|
||||||
|
entity,
|
||||||
|
direction: WalkDirection::Forward,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
jump_events.send(JumpEvent { entity });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_headhitter_parkour_move(
|
||||||
|
ExecuteCtx {
|
||||||
|
entity,
|
||||||
|
target,
|
||||||
|
start,
|
||||||
|
position,
|
||||||
|
look_at_events,
|
||||||
|
sprint_events,
|
||||||
|
walk_events,
|
||||||
|
jump_events,
|
||||||
|
..
|
||||||
|
}: ExecuteCtx,
|
||||||
|
) {
|
||||||
|
let center = target.center();
|
||||||
|
look_at_events.send(LookAtEvent {
|
||||||
|
entity,
|
||||||
|
position: center,
|
||||||
|
});
|
||||||
|
|
||||||
|
let jump_distance = i32::max((target - start).x.abs(), (target - start).z.abs());
|
||||||
|
|
||||||
|
if jump_distance > 2 {
|
||||||
|
sprint_events.send(StartSprintEvent {
|
||||||
|
entity,
|
||||||
|
direction: SprintDirection::Forward,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
walk_events.send(StartWalkEvent {
|
||||||
|
entity,
|
||||||
|
direction: WalkDirection::Forward,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_center = start.center();
|
||||||
|
let distance_from_start = f64::max(
|
||||||
|
(start_center.x as f64 - position.x).abs(),
|
||||||
|
(start_center.z as f64 - position.z).abs(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if distance_from_start > 0.75 {
|
||||||
|
jump_events.send(JumpEvent { entity });
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue