mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
simplify pathfinder
This commit is contained in:
parent
ac1522db54
commit
5d7669f72b
6 changed files with 405 additions and 214 deletions
|
@ -62,7 +62,7 @@ impl Direction {
|
|||
}
|
||||
|
||||
// TODO: make azalea_block use this instead of FacingCardinal
|
||||
#[derive(Clone, Copy, Debug, McBuf)]
|
||||
#[derive(Clone, Copy, Debug, McBuf, PartialEq, Eq, Hash)]
|
||||
pub enum CardinalDirection {
|
||||
North,
|
||||
South,
|
||||
|
|
|
@ -87,7 +87,9 @@ pub trait BotClientExt {
|
|||
impl BotClientExt for azalea_client::Client {
|
||||
fn jump(&mut self) {
|
||||
let mut ecs = self.ecs.lock();
|
||||
ecs.send_event(JumpEvent(self.entity));
|
||||
ecs.send_event(JumpEvent {
|
||||
entity: self.entity,
|
||||
});
|
||||
}
|
||||
|
||||
fn look_at(&mut self, position: Vec3) {
|
||||
|
@ -136,14 +138,16 @@ impl BotClientExt for azalea_client::Client {
|
|||
|
||||
/// Event to jump once.
|
||||
#[derive(Event)]
|
||||
pub struct JumpEvent(pub Entity);
|
||||
pub struct JumpEvent {
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
pub fn jump_listener(
|
||||
mut query: Query<(&mut Jumping, &mut Bot)>,
|
||||
mut events: EventReader<JumpEvent>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok((mut jumping, mut bot)) = query.get_mut(event.0) {
|
||||
if let Ok((mut jumping, mut bot)) = query.get_mut(event.entity) {
|
||||
**jumping = true;
|
||||
bot.jumping_once = true;
|
||||
}
|
||||
|
|
|
@ -1,110 +1,176 @@
|
|||
use std::{cmp::Reverse, collections::HashMap, fmt::Debug, hash::Hash, ops::Add};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use priority_queue::PriorityQueue;
|
||||
|
||||
pub fn a_star<N, W, HeuristicFn, SuccessorsFn, SuccessFn>(
|
||||
start: N,
|
||||
pub struct Path<P, M>
|
||||
where
|
||||
P: Eq + Hash + Copy + Debug,
|
||||
{
|
||||
pub movements: Vec<Movement<P, M>>,
|
||||
pub partial: bool,
|
||||
}
|
||||
|
||||
pub fn a_star<P, M, HeuristicFn, SuccessorsFn, SuccessFn>(
|
||||
start: P,
|
||||
heuristic: HeuristicFn,
|
||||
successors: SuccessorsFn,
|
||||
success: SuccessFn,
|
||||
) -> Option<Vec<N>>
|
||||
timeout: Duration,
|
||||
) -> Path<P, M>
|
||||
where
|
||||
N: Eq + Hash + Copy + Debug,
|
||||
W: PartialOrd + Default + Copy + num_traits::Bounded + Debug + Add<Output = W>,
|
||||
HeuristicFn: Fn(&N) -> W,
|
||||
SuccessorsFn: Fn(&N) -> Vec<Edge<N, W>>,
|
||||
SuccessFn: Fn(&N) -> bool,
|
||||
P: Eq + Hash + Copy + Debug,
|
||||
HeuristicFn: Fn(P) -> f32,
|
||||
SuccessorsFn: Fn(P) -> Vec<Edge<P, M>>,
|
||||
SuccessFn: Fn(P) -> bool,
|
||||
{
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut open_set = PriorityQueue::new();
|
||||
open_set.push(start, Reverse(Weight(W::default())));
|
||||
let mut nodes: HashMap<N, Node<N, W>> = HashMap::new();
|
||||
open_set.push(start, Reverse(Weight(0.)));
|
||||
let mut nodes: HashMap<P, Node<P, M>> = HashMap::new();
|
||||
nodes.insert(
|
||||
start,
|
||||
Node {
|
||||
data: start,
|
||||
position: start,
|
||||
movement_data: None,
|
||||
came_from: None,
|
||||
g_score: W::default(),
|
||||
f_score: W::max_value(),
|
||||
g_score: f32::default(),
|
||||
f_score: f32::MAX,
|
||||
},
|
||||
);
|
||||
|
||||
let mut best_node = start;
|
||||
let mut best_node_score = heuristic(start);
|
||||
|
||||
while let Some((current_node, _)) = open_set.pop() {
|
||||
if success(¤t_node) {
|
||||
return Some(reconstruct_path(&nodes, current_node));
|
||||
if success(current_node) {
|
||||
return Path {
|
||||
movements: reconstruct_path(nodes, current_node),
|
||||
partial: false,
|
||||
};
|
||||
}
|
||||
|
||||
let current_g_score = nodes
|
||||
.get(¤t_node)
|
||||
.map(|n| n.g_score)
|
||||
.unwrap_or(W::max_value());
|
||||
.unwrap_or(f32::MAX);
|
||||
|
||||
for neighbor in successors(¤t_node) {
|
||||
for neighbor in successors(current_node) {
|
||||
let tentative_g_score = current_g_score + neighbor.cost;
|
||||
let neighbor_g_score = nodes
|
||||
.get(&neighbor.target)
|
||||
.get(&neighbor.movement.target)
|
||||
.map(|n| n.g_score)
|
||||
.unwrap_or(W::max_value());
|
||||
.unwrap_or(f32::MAX);
|
||||
if tentative_g_score < neighbor_g_score {
|
||||
let f_score = tentative_g_score + heuristic(&neighbor.target);
|
||||
let heuristic = heuristic(neighbor.movement.target);
|
||||
let f_score = tentative_g_score + heuristic;
|
||||
nodes.insert(
|
||||
neighbor.target,
|
||||
neighbor.movement.target,
|
||||
Node {
|
||||
data: neighbor.target,
|
||||
position: neighbor.movement.target,
|
||||
movement_data: Some(neighbor.movement.data),
|
||||
came_from: Some(current_node),
|
||||
g_score: tentative_g_score,
|
||||
f_score,
|
||||
},
|
||||
);
|
||||
open_set.push(neighbor.target, Reverse(Weight(f_score)));
|
||||
open_set.push(neighbor.movement.target, Reverse(Weight(f_score)));
|
||||
|
||||
let node_score = heuristic + tentative_g_score / 1.5;
|
||||
if node_score < best_node_score {
|
||||
best_node = neighbor.movement.target;
|
||||
best_node_score = node_score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
if start_time.elapsed() > timeout {
|
||||
// timeout, just return the best path we have so far
|
||||
info!("Pathfinder timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn reconstruct_path<N, W>(nodes: &HashMap<N, Node<N, W>>, current: N) -> Vec<N>
|
||||
Path {
|
||||
movements: reconstruct_path(nodes, best_node),
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn reconstruct_path<P, M>(mut nodes: HashMap<P, Node<P, M>>, current: P) -> Vec<Movement<P, M>>
|
||||
where
|
||||
N: Eq + Hash + Copy + Debug,
|
||||
W: PartialOrd + Default + Copy + num_traits::Bounded + Debug,
|
||||
P: Eq + Hash + Copy + Debug,
|
||||
{
|
||||
let mut path = vec![current];
|
||||
let mut path = Vec::new();
|
||||
let mut current = current;
|
||||
while let Some(node) = nodes.get(¤t) {
|
||||
while let Some(node) = nodes.remove(¤t) {
|
||||
if let Some(came_from) = node.came_from {
|
||||
path.push(came_from);
|
||||
current = came_from;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
path.push(Movement {
|
||||
target: node.position,
|
||||
data: node.movement_data.unwrap(),
|
||||
});
|
||||
}
|
||||
path.reverse();
|
||||
path
|
||||
}
|
||||
|
||||
pub struct Node<N, W> {
|
||||
pub data: N,
|
||||
pub came_from: Option<N>,
|
||||
pub g_score: W,
|
||||
pub f_score: W,
|
||||
pub struct Node<P, M> {
|
||||
pub position: P,
|
||||
pub movement_data: Option<M>,
|
||||
pub came_from: Option<P>,
|
||||
pub g_score: f32,
|
||||
pub f_score: f32,
|
||||
}
|
||||
|
||||
pub struct Edge<N: Eq + Hash + Copy, W: PartialOrd + Copy> {
|
||||
pub target: N,
|
||||
pub cost: W,
|
||||
pub struct Edge<P: Hash + Copy, M> {
|
||||
pub movement: Movement<P, M>,
|
||||
pub cost: f32,
|
||||
}
|
||||
|
||||
pub struct Movement<P: Hash + Copy, M> {
|
||||
pub target: P,
|
||||
pub data: M,
|
||||
}
|
||||
|
||||
impl<P: Hash + Copy + Debug, M: Debug> Debug for Movement<P, M> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Movement")
|
||||
.field("target", &self.target)
|
||||
.field("data", &self.data)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<P: Hash + Copy + Clone, M: Clone> Clone for Movement<P, M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
target: self.target,
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct Weight<W: PartialOrd + Debug>(W);
|
||||
impl<W: PartialOrd + Debug> Ord for Weight<W> {
|
||||
pub struct Weight(f32);
|
||||
impl Ord for Weight {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0
|
||||
.partial_cmp(&other.0)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
}
|
||||
}
|
||||
impl<W: PartialOrd + Debug> Eq for Weight<W> {}
|
||||
impl<W: PartialOrd + Debug> PartialOrd for Weight<W> {
|
||||
impl Eq for Weight {}
|
||||
impl PartialOrd for Weight {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
use azalea_core::BlockPos;
|
||||
|
||||
use super::{Goal, Node, VerticalVel};
|
||||
use super::Goal;
|
||||
|
||||
pub struct BlockPosGoal {
|
||||
pub pos: BlockPos,
|
||||
}
|
||||
impl Goal for BlockPosGoal {
|
||||
fn heuristic(&self, n: &Node) -> f32 {
|
||||
let dx = (self.pos.x - n.pos.x) as f32;
|
||||
let dy = (self.pos.y - n.pos.y) as f32;
|
||||
let dz = (self.pos.z - n.pos.z) as f32;
|
||||
fn heuristic(&self, n: BlockPos) -> f32 {
|
||||
let dx = (self.pos.x - n.x) as f32;
|
||||
let dy = (self.pos.y - n.y) as f32;
|
||||
let dz = (self.pos.z - n.z) as f32;
|
||||
dx * dx + dy * dy + dz * dz
|
||||
}
|
||||
fn success(&self, n: &Node) -> bool {
|
||||
n.pos == self.pos
|
||||
}
|
||||
fn goal_node(&self) -> Node {
|
||||
Node {
|
||||
pos: self.pos,
|
||||
vertical_vel: VerticalVel::None,
|
||||
fn success(&self, n: BlockPos) -> bool {
|
||||
n == self.pos
|
||||
}
|
||||
fn goal_node(&self) -> BlockPos {
|
||||
self.pos
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ pub mod simulation;
|
|||
|
||||
use crate::bot::{JumpEvent, LookAtEvent};
|
||||
use crate::pathfinder::astar::a_star;
|
||||
use crate::{SprintDirection, WalkDirection};
|
||||
use crate::pathfinder::moves::DefaultMoves;
|
||||
use crate::WalkDirection;
|
||||
|
||||
use crate::app::{App, Plugin};
|
||||
use crate::ecs::{
|
||||
|
@ -15,7 +16,6 @@ use crate::ecs::{
|
|||
query::{With, Without},
|
||||
system::{Commands, Query, Res},
|
||||
};
|
||||
use astar::Edge;
|
||||
use azalea_client::movement::walk_listener;
|
||||
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||
use azalea_core::{BlockPos, CardinalDirection};
|
||||
|
@ -33,6 +33,9 @@ use futures_lite::future;
|
|||
use log::{debug, error, trace};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use self::moves::ExecuteCtx;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PathfinderPlugin;
|
||||
|
@ -65,7 +68,7 @@ impl Plugin for PathfinderPlugin {
|
|||
/// A component that makes this entity able to pathfind.
|
||||
#[derive(Component, Default)]
|
||||
pub struct Pathfinder {
|
||||
pub path: VecDeque<Node>,
|
||||
pub path: VecDeque<astar::Movement<BlockPos, moves::MoveData>>,
|
||||
}
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn add_default_pathfinder(
|
||||
|
@ -104,7 +107,7 @@ pub struct GotoEvent {
|
|||
#[derive(Event)]
|
||||
pub struct PathFoundEvent {
|
||||
pub entity: Entity,
|
||||
pub path: VecDeque<Node>,
|
||||
pub path: Option<VecDeque<astar::Movement<BlockPos, moves::MoveData>>>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
@ -122,10 +125,7 @@ fn goto_listener(
|
|||
let (position, world_name) = query
|
||||
.get_mut(event.entity)
|
||||
.expect("Called goto on an entity that's not in the world");
|
||||
let start = Node {
|
||||
pos: BlockPos::from(position),
|
||||
vertical_vel: VerticalVel::None,
|
||||
};
|
||||
let start = BlockPos::from(position);
|
||||
|
||||
let world_lock = instance_container
|
||||
.get(world_name)
|
||||
|
@ -138,64 +138,60 @@ fn goto_listener(
|
|||
let task = thread_pool.spawn(async move {
|
||||
debug!("start: {start:?}, end: {end:?}");
|
||||
|
||||
let possible_moves: Vec<&dyn moves::Move> = vec![
|
||||
&moves::ForwardMove(CardinalDirection::North),
|
||||
&moves::ForwardMove(CardinalDirection::East),
|
||||
&moves::ForwardMove(CardinalDirection::South),
|
||||
&moves::ForwardMove(CardinalDirection::West),
|
||||
let possible_moves: Vec<DefaultMoves> = vec![
|
||||
DefaultMoves::Forward(CardinalDirection::North),
|
||||
DefaultMoves::Forward(CardinalDirection::East),
|
||||
DefaultMoves::Forward(CardinalDirection::South),
|
||||
DefaultMoves::Forward(CardinalDirection::West),
|
||||
//
|
||||
&moves::AscendMove(CardinalDirection::North),
|
||||
&moves::AscendMove(CardinalDirection::East),
|
||||
&moves::AscendMove(CardinalDirection::South),
|
||||
&moves::AscendMove(CardinalDirection::West),
|
||||
DefaultMoves::Ascend(CardinalDirection::North),
|
||||
DefaultMoves::Ascend(CardinalDirection::East),
|
||||
DefaultMoves::Ascend(CardinalDirection::South),
|
||||
DefaultMoves::Ascend(CardinalDirection::West),
|
||||
//
|
||||
&moves::DescendMove(CardinalDirection::North),
|
||||
&moves::DescendMove(CardinalDirection::East),
|
||||
&moves::DescendMove(CardinalDirection::South),
|
||||
&moves::DescendMove(CardinalDirection::West),
|
||||
DefaultMoves::Descend(CardinalDirection::North),
|
||||
DefaultMoves::Descend(CardinalDirection::East),
|
||||
DefaultMoves::Descend(CardinalDirection::South),
|
||||
DefaultMoves::Descend(CardinalDirection::West),
|
||||
//
|
||||
&moves::DiagonalMove(CardinalDirection::North),
|
||||
&moves::DiagonalMove(CardinalDirection::East),
|
||||
&moves::DiagonalMove(CardinalDirection::South),
|
||||
&moves::DiagonalMove(CardinalDirection::West),
|
||||
DefaultMoves::Diagonal(CardinalDirection::North),
|
||||
DefaultMoves::Diagonal(CardinalDirection::East),
|
||||
DefaultMoves::Diagonal(CardinalDirection::South),
|
||||
DefaultMoves::Diagonal(CardinalDirection::West),
|
||||
];
|
||||
|
||||
let successors = |node: &Node| {
|
||||
let successors = |pos: BlockPos| {
|
||||
let mut edges = Vec::new();
|
||||
|
||||
let world = world_lock.read();
|
||||
for possible_move in &possible_moves {
|
||||
let possible_move = possible_move.get(&world, node);
|
||||
if let Some(possible_move) = possible_move {
|
||||
edges.push(Edge {
|
||||
target: possible_move.node,
|
||||
cost: possible_move.cost,
|
||||
});
|
||||
let move_result = possible_move.get(&world, pos);
|
||||
if let Some(edge) = move_result {
|
||||
edges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
edges
|
||||
};
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
let p = a_star(
|
||||
let astar::Path { movements, partial } = a_star(
|
||||
start,
|
||||
|n| goal.heuristic(n),
|
||||
successors,
|
||||
|n| goal.success(n),
|
||||
Duration::from_secs(1),
|
||||
);
|
||||
let end_time = std::time::Instant::now();
|
||||
debug!("path: {p:?}");
|
||||
debug!("movements: {movements:?}");
|
||||
debug!("partial: {partial:?}");
|
||||
debug!("time: {:?}", end_time - start_time);
|
||||
|
||||
// convert the Option<Vec<Node>> to a VecDeque<Node>
|
||||
if let Some(p) = p {
|
||||
let path = p.into_iter().collect::<VecDeque<_>>();
|
||||
// commands.entity(event.entity).insert(Pathfinder { path: p });
|
||||
Some(PathFoundEvent { entity, path })
|
||||
} else {
|
||||
error!("no path found");
|
||||
None
|
||||
}
|
||||
let path = movements.into_iter().collect::<VecDeque<_>>();
|
||||
Some(PathFoundEvent {
|
||||
entity,
|
||||
path: Some(path),
|
||||
})
|
||||
});
|
||||
|
||||
commands.spawn(ComputePath(task));
|
||||
|
@ -226,7 +222,12 @@ fn path_found_listener(mut events: EventReader<PathFoundEvent>, mut query: Query
|
|||
let mut pathfinder = query
|
||||
.get_mut(event.entity)
|
||||
.expect("Path found for an entity that doesn't have a pathfinder");
|
||||
pathfinder.path = event.path.clone();
|
||||
if let Some(path) = &event.path {
|
||||
pathfinder.path = path.to_owned();
|
||||
} else {
|
||||
error!("No path found");
|
||||
pathfinder.path.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,30 +240,12 @@ fn tick_execute_path(
|
|||
) {
|
||||
for (entity, mut pathfinder, position, physics) in &mut query {
|
||||
loop {
|
||||
let Some(target) = pathfinder.path.front() else {
|
||||
let Some(movement) = pathfinder.path.front() else {
|
||||
break;
|
||||
};
|
||||
let center = target.pos.center();
|
||||
// println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos());
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
trace!(
|
||||
"tick: pathfinder {entity:?}; going to {:?}; currently at {:?}",
|
||||
target.pos,
|
||||
**position
|
||||
);
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
// check if we should jump
|
||||
if target.pos.y > position.y.floor() as i32 {
|
||||
jump_events.send(JumpEvent(entity));
|
||||
}
|
||||
|
||||
if target.is_reached(position, physics) {
|
||||
// we check if the goal was reached *before* actually executing the movement so
|
||||
// we don't unnecessarily execute a movement when it wasn't necessary
|
||||
if is_goal_reached(movement.target, position, physics) {
|
||||
// println!("reached target");
|
||||
pathfinder.path.pop_front();
|
||||
if pathfinder.path.is_empty() {
|
||||
|
@ -273,9 +256,21 @@ fn tick_execute_path(
|
|||
});
|
||||
}
|
||||
// tick again, maybe we already reached the next node!
|
||||
} else {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
let ctx = ExecuteCtx {
|
||||
entity,
|
||||
target: movement.target,
|
||||
position: **position,
|
||||
look_at_events: &mut look_at_events,
|
||||
sprint_events: &mut sprint_events,
|
||||
walk_events: &mut walk_events,
|
||||
jump_events: &mut jump_events,
|
||||
};
|
||||
trace!("executing move {:?}", movement.data.move_kind);
|
||||
movement.data.move_kind.execute(ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,35 +291,18 @@ fn stop_pathfinding_on_instance_change(
|
|||
}
|
||||
}
|
||||
|
||||
/// Information about our vertical velocity
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
|
||||
pub enum VerticalVel {
|
||||
None,
|
||||
/// No vertical velocity, but we're not on the ground
|
||||
NoneMidair,
|
||||
// less than 3 blocks (no fall damage)
|
||||
FallingLittle,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
|
||||
pub struct Node {
|
||||
pub pos: BlockPos,
|
||||
pub vertical_vel: VerticalVel,
|
||||
}
|
||||
|
||||
pub trait Goal {
|
||||
fn heuristic(&self, n: &Node) -> f32;
|
||||
fn success(&self, n: &Node) -> bool;
|
||||
fn heuristic(&self, n: BlockPos) -> f32;
|
||||
fn success(&self, n: BlockPos) -> bool;
|
||||
// TODO: this should be removed and mtdstarlite should stop depending on
|
||||
// being given a goal node
|
||||
fn goal_node(&self) -> Node;
|
||||
fn goal_node(&self) -> BlockPos;
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Returns whether the entity is at the node and should start going to the
|
||||
/// next node.
|
||||
#[must_use]
|
||||
pub fn is_reached(&self, position: &Position, physics: &Physics) -> bool {
|
||||
pub fn is_goal_reached(goal_pos: BlockPos, current_pos: &Position, physics: &Physics) -> bool {
|
||||
// println!(
|
||||
// "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}",
|
||||
// entity.delta.y,
|
||||
|
@ -332,13 +310,7 @@ impl Node {
|
|||
// self.pos,
|
||||
// self.vertical_vel
|
||||
// );
|
||||
BlockPos::from(position) == self.pos
|
||||
&& match self.vertical_vel {
|
||||
VerticalVel::NoneMidair => physics.delta.y > -0.1 && physics.delta.y < 0.1,
|
||||
VerticalVel::None => physics.on_ground,
|
||||
VerticalVel::FallingLittle => physics.delta.y < -0.1,
|
||||
}
|
||||
}
|
||||
BlockPos::from(current_pos) == goal_pos && physics.on_ground
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
use super::{Node, VerticalVel};
|
||||
use azalea_core::{BlockPos, CardinalDirection};
|
||||
use crate::{JumpEvent, LookAtEvent};
|
||||
|
||||
use super::astar;
|
||||
use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent};
|
||||
use azalea_core::{BlockPos, CardinalDirection, Vec3};
|
||||
use azalea_physics::collision::{self, BlockWithShape};
|
||||
use azalea_world::Instance;
|
||||
use bevy_ecs::{entity::Entity, event::EventWriter};
|
||||
|
||||
type Edge = astar::Edge<BlockPos, MoveData>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MoveData {
|
||||
pub move_kind: DefaultMoves,
|
||||
}
|
||||
|
||||
/// whether this block is passable
|
||||
fn is_block_passable(pos: &BlockPos, world: &Instance) -> bool {
|
||||
if let Some(block) = world.chunks.get_block_state(pos) {
|
||||
if block.shape() != &collision::empty_shape() {
|
||||
return false;
|
||||
}
|
||||
if block == azalea_registry::Block::Water.into() {
|
||||
return false;
|
||||
}
|
||||
if block.waterlogged() {
|
||||
return false;
|
||||
}
|
||||
block.shape() == &collision::empty_shape()
|
||||
} else {
|
||||
false
|
||||
|
@ -15,8 +35,7 @@ fn is_block_passable(pos: &BlockPos, world: &Instance) -> bool {
|
|||
/// whether this block has a solid hitbox (i.e. we can stand on it)
|
||||
fn is_block_solid(pos: &BlockPos, world: &Instance) -> bool {
|
||||
if let Some(block) = world.chunks.get_block_state(pos) {
|
||||
// block.shape() == &collision::block_shape()
|
||||
block.shape() != &collision::empty_shape()
|
||||
block.shape() == &collision::block_shape()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -53,62 +72,148 @@ const JUMP_COST: f32 = 0.5;
|
|||
const WALK_ONE_BLOCK_COST: f32 = 1.0;
|
||||
const FALL_ONE_BLOCK_COST: f32 = 0.5;
|
||||
|
||||
pub trait Move: Send + Sync {
|
||||
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult>;
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum DefaultMoves {
|
||||
Forward(CardinalDirection),
|
||||
Ascend(CardinalDirection),
|
||||
Descend(CardinalDirection),
|
||||
Diagonal(CardinalDirection),
|
||||
}
|
||||
pub struct MoveResult {
|
||||
pub node: Node,
|
||||
pub cost: f32,
|
||||
|
||||
impl DefaultMoves {
|
||||
pub fn get(self, world: &Instance, node: BlockPos) -> Option<Edge> {
|
||||
match self {
|
||||
DefaultMoves::Forward(dir) => ForwardMove(dir).get(world, node),
|
||||
DefaultMoves::Ascend(dir) => AscendMove(dir).get(world, node),
|
||||
DefaultMoves::Descend(dir) => DescendMove(dir).get(world, node),
|
||||
DefaultMoves::Diagonal(dir) => DiagonalMove(dir).get(world, node),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(self, ctx: ExecuteCtx) {
|
||||
match self {
|
||||
DefaultMoves::Forward(_) => ForwardMove::execute(ctx),
|
||||
DefaultMoves::Ascend(_) => AscendMove::execute(ctx),
|
||||
DefaultMoves::Descend(_) => DescendMove::execute(ctx),
|
||||
DefaultMoves::Diagonal(_) => DiagonalMove::execute(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MoveImpl: Send + Sync {
|
||||
fn get(&self, world: &Instance, node: BlockPos) -> Option<Edge>;
|
||||
fn execute(ctx: ExecuteCtx);
|
||||
}
|
||||
|
||||
pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'a> {
|
||||
pub entity: Entity,
|
||||
pub target: BlockPos,
|
||||
pub position: Vec3,
|
||||
|
||||
pub look_at_events: &'a mut EventWriter<'w1, LookAtEvent>,
|
||||
pub sprint_events: &'a mut EventWriter<'w2, StartSprintEvent>,
|
||||
pub walk_events: &'a mut EventWriter<'w3, StartWalkEvent>,
|
||||
pub jump_events: &'a mut EventWriter<'w4, JumpEvent>,
|
||||
}
|
||||
|
||||
pub struct ForwardMove(pub CardinalDirection);
|
||||
impl Move for ForwardMove {
|
||||
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> {
|
||||
impl MoveImpl for ForwardMove {
|
||||
fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
|
||||
let offset = BlockPos::new(self.0.x(), 0, self.0.z());
|
||||
|
||||
if !is_standable(&(node.pos + offset), world) || node.vertical_vel != VerticalVel::None {
|
||||
if !is_standable(&(pos + offset), world) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cost = WALK_ONE_BLOCK_COST;
|
||||
|
||||
Some(MoveResult {
|
||||
node: Node {
|
||||
pos: node.pos + offset,
|
||||
vertical_vel: VerticalVel::None,
|
||||
Some(Edge {
|
||||
movement: astar::Movement {
|
||||
target: pos + offset,
|
||||
data: MoveData {
|
||||
move_kind: DefaultMoves::Forward(self.0),
|
||||
},
|
||||
},
|
||||
cost,
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(
|
||||
ExecuteCtx {
|
||||
entity,
|
||||
target,
|
||||
look_at_events,
|
||||
sprint_events,
|
||||
..
|
||||
}: ExecuteCtx,
|
||||
) {
|
||||
let center = target.center();
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AscendMove(pub CardinalDirection);
|
||||
impl Move for AscendMove {
|
||||
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> {
|
||||
impl MoveImpl for AscendMove {
|
||||
fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
|
||||
let offset = BlockPos::new(self.0.x(), 1, self.0.z());
|
||||
|
||||
if node.vertical_vel != VerticalVel::None
|
||||
|| !is_block_passable(&node.pos.up(2), world)
|
||||
|| !is_standable(&(node.pos + offset), world)
|
||||
{
|
||||
if !is_block_passable(&pos.up(2), world) || !is_standable(&(pos + offset), world) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cost = WALK_ONE_BLOCK_COST + JUMP_COST;
|
||||
|
||||
Some(MoveResult {
|
||||
node: Node {
|
||||
pos: node.pos + offset,
|
||||
vertical_vel: VerticalVel::None,
|
||||
// Some(MoveResult {
|
||||
// node: Node {
|
||||
// pos: node.pos + offset,
|
||||
// vertical_vel: VerticalVel::None,
|
||||
// },
|
||||
// cost,
|
||||
// })
|
||||
Some(Edge {
|
||||
movement: astar::Movement {
|
||||
target: pos + offset,
|
||||
data: MoveData {
|
||||
move_kind: DefaultMoves::Ascend(self.0),
|
||||
},
|
||||
},
|
||||
cost,
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(
|
||||
ExecuteCtx {
|
||||
entity,
|
||||
target,
|
||||
look_at_events,
|
||||
sprint_events,
|
||||
jump_events,
|
||||
..
|
||||
}: ExecuteCtx,
|
||||
) {
|
||||
let center = target.center();
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
jump_events.send(JumpEvent { entity });
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
}
|
||||
}
|
||||
pub struct DescendMove(pub CardinalDirection);
|
||||
impl Move for DescendMove {
|
||||
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> {
|
||||
let new_horizontal_position = node.pos + BlockPos::new(self.0.x(), 0, self.0.z());
|
||||
impl MoveImpl for DescendMove {
|
||||
fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
|
||||
let new_horizontal_position = pos + BlockPos::new(self.0.x(), 0, self.0.z());
|
||||
let fall_distance = fall_distance(&new_horizontal_position, world);
|
||||
if fall_distance == 0 {
|
||||
return None;
|
||||
|
@ -119,57 +224,104 @@ impl Move for DescendMove {
|
|||
let new_position = new_horizontal_position.down(fall_distance as i32);
|
||||
|
||||
// check whether 3 blocks vertically forward are passable
|
||||
if node.vertical_vel != VerticalVel::None || !is_passable(&new_horizontal_position, world) {
|
||||
if !is_passable(&new_horizontal_position, world) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cost = WALK_ONE_BLOCK_COST + FALL_ONE_BLOCK_COST * fall_distance as f32;
|
||||
|
||||
Some(MoveResult {
|
||||
node: Node {
|
||||
pos: new_position,
|
||||
vertical_vel: VerticalVel::None,
|
||||
Some(Edge {
|
||||
movement: astar::Movement {
|
||||
target: new_position,
|
||||
data: MoveData {
|
||||
move_kind: DefaultMoves::Descend(self.0),
|
||||
},
|
||||
},
|
||||
cost,
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(
|
||||
ExecuteCtx {
|
||||
entity,
|
||||
target,
|
||||
look_at_events,
|
||||
sprint_events,
|
||||
..
|
||||
}: ExecuteCtx,
|
||||
) {
|
||||
let center = target.center();
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
}
|
||||
}
|
||||
pub struct DiagonalMove(pub CardinalDirection);
|
||||
impl Move for DiagonalMove {
|
||||
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> {
|
||||
if node.vertical_vel != VerticalVel::None {
|
||||
return None;
|
||||
}
|
||||
|
||||
impl MoveImpl for DiagonalMove {
|
||||
fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
|
||||
let right = self.0.right();
|
||||
let offset = BlockPos::new(self.0.x() + right.x(), 0, self.0.z() + right.z());
|
||||
|
||||
if !is_passable(
|
||||
&BlockPos::new(node.pos.x + self.0.x(), node.pos.y, node.pos.z + self.0.z()),
|
||||
&BlockPos::new(pos.x + self.0.x(), pos.y, pos.z + self.0.z()),
|
||||
world,
|
||||
) && !is_passable(
|
||||
&BlockPos::new(
|
||||
node.pos.x + self.0.right().x(),
|
||||
node.pos.y,
|
||||
node.pos.z + self.0.right().z(),
|
||||
pos.x + self.0.right().x(),
|
||||
pos.y,
|
||||
pos.z + self.0.right().z(),
|
||||
),
|
||||
world,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
if !is_standable(&(node.pos + offset), world) {
|
||||
if !is_standable(&(pos + offset), world) {
|
||||
return None;
|
||||
}
|
||||
let cost = WALK_ONE_BLOCK_COST * 1.4;
|
||||
|
||||
Some(MoveResult {
|
||||
node: Node {
|
||||
pos: node.pos + offset,
|
||||
vertical_vel: VerticalVel::None,
|
||||
// Some(MoveResult {
|
||||
// node: Node {
|
||||
// pos: node.pos + offset,
|
||||
// vertical_vel: VerticalVel::None,
|
||||
// },
|
||||
// cost,
|
||||
// })
|
||||
Some(Edge {
|
||||
movement: astar::Movement {
|
||||
target: pos + offset,
|
||||
data: MoveData {
|
||||
move_kind: DefaultMoves::Diagonal(self.0),
|
||||
},
|
||||
},
|
||||
cost,
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(
|
||||
ExecuteCtx {
|
||||
entity,
|
||||
target,
|
||||
look_at_events,
|
||||
sprint_events,
|
||||
..
|
||||
}: ExecuteCtx,
|
||||
) {
|
||||
let center = target.center();
|
||||
look_at_events.send(LookAtEvent {
|
||||
entity,
|
||||
position: center,
|
||||
});
|
||||
sprint_events.send(StartSprintEvent {
|
||||
entity,
|
||||
direction: SprintDirection::Forward,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Reference in a new issue