1
2
Fork 0
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:
mat 2023-08-26 03:08:35 -05:00
parent ac1522db54
commit 5d7669f72b
6 changed files with 405 additions and 214 deletions

View file

@ -62,7 +62,7 @@ impl Direction {
} }
// TODO: make azalea_block use this instead of FacingCardinal // 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 { pub enum CardinalDirection {
North, North,
South, South,

View file

@ -87,7 +87,9 @@ pub trait BotClientExt {
impl BotClientExt for azalea_client::Client { impl BotClientExt for azalea_client::Client {
fn jump(&mut self) { fn jump(&mut self) {
let mut ecs = self.ecs.lock(); 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) { fn look_at(&mut self, position: Vec3) {
@ -136,14 +138,16 @@ impl BotClientExt for azalea_client::Client {
/// Event to jump once. /// Event to jump once.
#[derive(Event)] #[derive(Event)]
pub struct JumpEvent(pub Entity); pub struct JumpEvent {
pub entity: Entity,
}
pub fn jump_listener( pub fn jump_listener(
mut query: Query<(&mut Jumping, &mut Bot)>, mut query: Query<(&mut Jumping, &mut Bot)>,
mut events: EventReader<JumpEvent>, mut events: EventReader<JumpEvent>,
) { ) {
for event in events.iter() { 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; **jumping = true;
bot.jumping_once = true; bot.jumping_once = true;
} }

View file

@ -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; use priority_queue::PriorityQueue;
pub fn a_star<N, W, HeuristicFn, SuccessorsFn, SuccessFn>( pub struct Path<P, M>
start: N, 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, heuristic: HeuristicFn,
successors: SuccessorsFn, successors: SuccessorsFn,
success: SuccessFn, success: SuccessFn,
) -> Option<Vec<N>> timeout: Duration,
) -> Path<P, M>
where where
N: Eq + Hash + Copy + Debug, P: Eq + Hash + Copy + Debug,
W: PartialOrd + Default + Copy + num_traits::Bounded + Debug + Add<Output = W>, HeuristicFn: Fn(P) -> f32,
HeuristicFn: Fn(&N) -> W, SuccessorsFn: Fn(P) -> Vec<Edge<P, M>>,
SuccessorsFn: Fn(&N) -> Vec<Edge<N, W>>, SuccessFn: Fn(P) -> bool,
SuccessFn: Fn(&N) -> bool,
{ {
let start_time = Instant::now();
let mut open_set = PriorityQueue::new(); let mut open_set = PriorityQueue::new();
open_set.push(start, Reverse(Weight(W::default()))); open_set.push(start, Reverse(Weight(0.)));
let mut nodes: HashMap<N, Node<N, W>> = HashMap::new(); let mut nodes: HashMap<P, Node<P, M>> = HashMap::new();
nodes.insert( nodes.insert(
start, start,
Node { Node {
data: start, position: start,
movement_data: None,
came_from: None, came_from: None,
g_score: W::default(), g_score: f32::default(),
f_score: W::max_value(), f_score: f32::MAX,
}, },
); );
let mut best_node = start;
let mut best_node_score = heuristic(start);
while let Some((current_node, _)) = open_set.pop() { while let Some((current_node, _)) = open_set.pop() {
if success(&current_node) { if success(current_node) {
return Some(reconstruct_path(&nodes, current_node)); return Path {
movements: reconstruct_path(nodes, current_node),
partial: false,
};
} }
let current_g_score = nodes let current_g_score = nodes
.get(&current_node) .get(&current_node)
.map(|n| n.g_score) .map(|n| n.g_score)
.unwrap_or(W::max_value()); .unwrap_or(f32::MAX);
for neighbor in successors(&current_node) { for neighbor in successors(current_node) {
let tentative_g_score = current_g_score + neighbor.cost; let tentative_g_score = current_g_score + neighbor.cost;
let neighbor_g_score = nodes let neighbor_g_score = nodes
.get(&neighbor.target) .get(&neighbor.movement.target)
.map(|n| n.g_score) .map(|n| n.g_score)
.unwrap_or(W::max_value()); .unwrap_or(f32::MAX);
if tentative_g_score < neighbor_g_score { 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( nodes.insert(
neighbor.target, neighbor.movement.target,
Node { Node {
data: neighbor.target, position: neighbor.movement.target,
movement_data: Some(neighbor.movement.data),
came_from: Some(current_node), came_from: Some(current_node),
g_score: tentative_g_score, g_score: tentative_g_score,
f_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;
}
} }
} }
if start_time.elapsed() > timeout {
// timeout, just return the best path we have so far
info!("Pathfinder timeout");
break;
}
} }
None Path {
movements: reconstruct_path(nodes, best_node),
partial: true,
}
} }
fn reconstruct_path<N, W>(nodes: &HashMap<N, Node<N, W>>, current: N) -> Vec<N> fn reconstruct_path<P, M>(mut nodes: HashMap<P, Node<P, M>>, current: P) -> Vec<Movement<P, M>>
where where
N: Eq + Hash + Copy + Debug, P: Eq + Hash + Copy + Debug,
W: PartialOrd + Default + Copy + num_traits::Bounded + Debug,
{ {
let mut path = vec![current]; let mut path = Vec::new();
let mut current = current; let mut current = current;
while let Some(node) = nodes.get(&current) { while let Some(node) = nodes.remove(&current) {
if let Some(came_from) = node.came_from { if let Some(came_from) = node.came_from {
path.push(came_from);
current = came_from; current = came_from;
} else { } else {
break; break;
} }
path.push(Movement {
target: node.position,
data: node.movement_data.unwrap(),
});
} }
path.reverse(); path.reverse();
path path
} }
pub struct Node<N, W> { pub struct Node<P, M> {
pub data: N, pub position: P,
pub came_from: Option<N>, pub movement_data: Option<M>,
pub g_score: W, pub came_from: Option<P>,
pub f_score: W, pub g_score: f32,
pub f_score: f32,
} }
pub struct Edge<N: Eq + Hash + Copy, W: PartialOrd + Copy> { pub struct Edge<P: Hash + Copy, M> {
pub target: N, pub movement: Movement<P, M>,
pub cost: W, 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)] #[derive(PartialEq)]
pub struct Weight<W: PartialOrd + Debug>(W); pub struct Weight(f32);
impl<W: PartialOrd + Debug> Ord for Weight<W> { impl Ord for Weight {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0 self.0
.partial_cmp(&other.0) .partial_cmp(&other.0)
.unwrap_or(std::cmp::Ordering::Equal) .unwrap_or(std::cmp::Ordering::Equal)
} }
} }
impl<W: PartialOrd + Debug> Eq for Weight<W> {} impl Eq for Weight {}
impl<W: PartialOrd + Debug> PartialOrd for Weight<W> { impl PartialOrd for Weight {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }

View file

@ -1,25 +1,22 @@
use azalea_core::BlockPos; use azalea_core::BlockPos;
use super::{Goal, Node, VerticalVel}; use super::Goal;
pub struct BlockPosGoal { pub struct BlockPosGoal {
pub pos: BlockPos, pub pos: BlockPos,
} }
impl Goal for BlockPosGoal { impl Goal for BlockPosGoal {
fn heuristic(&self, n: &Node) -> f32 { fn heuristic(&self, n: BlockPos) -> f32 {
let dx = (self.pos.x - n.pos.x) as f32; let dx = (self.pos.x - n.x) as f32;
let dy = (self.pos.y - n.pos.y) as f32; let dy = (self.pos.y - n.y) as f32;
let dz = (self.pos.z - n.pos.z) as f32; let dz = (self.pos.z - n.z) as f32;
dx * dx + dy * dy + dz * dz dx * dx + dy * dy + dz * dz
} }
fn success(&self, n: &Node) -> bool { fn success(&self, n: BlockPos) -> bool {
n.pos == self.pos n == self.pos
} }
fn goal_node(&self) -> Node { fn goal_node(&self) -> BlockPos {
Node { self.pos
pos: self.pos,
vertical_vel: VerticalVel::None,
}
} }
} }

View file

@ -5,7 +5,8 @@ pub mod simulation;
use crate::bot::{JumpEvent, LookAtEvent}; use crate::bot::{JumpEvent, LookAtEvent};
use crate::pathfinder::astar::a_star; 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::app::{App, Plugin};
use crate::ecs::{ use crate::ecs::{
@ -15,7 +16,6 @@ use crate::ecs::{
query::{With, Without}, query::{With, Without},
system::{Commands, Query, Res}, system::{Commands, Query, Res},
}; };
use astar::Edge;
use azalea_client::movement::walk_listener; use azalea_client::movement::walk_listener;
use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_client::{StartSprintEvent, StartWalkEvent};
use azalea_core::{BlockPos, CardinalDirection}; use azalea_core::{BlockPos, CardinalDirection};
@ -33,6 +33,9 @@ use futures_lite::future;
use log::{debug, error, trace}; use log::{debug, error, trace};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use self::moves::ExecuteCtx;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct PathfinderPlugin; pub struct PathfinderPlugin;
@ -65,7 +68,7 @@ impl Plugin for PathfinderPlugin {
/// A component that makes this entity able to pathfind. /// A component that makes this entity able to pathfind.
#[derive(Component, Default)] #[derive(Component, Default)]
pub struct Pathfinder { pub struct Pathfinder {
pub path: VecDeque<Node>, pub path: VecDeque<astar::Movement<BlockPos, moves::MoveData>>,
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn add_default_pathfinder( fn add_default_pathfinder(
@ -104,7 +107,7 @@ pub struct GotoEvent {
#[derive(Event)] #[derive(Event)]
pub struct PathFoundEvent { pub struct PathFoundEvent {
pub entity: Entity, pub entity: Entity,
pub path: VecDeque<Node>, pub path: Option<VecDeque<astar::Movement<BlockPos, moves::MoveData>>>,
} }
#[derive(Component)] #[derive(Component)]
@ -122,10 +125,7 @@ fn goto_listener(
let (position, world_name) = query let (position, world_name) = query
.get_mut(event.entity) .get_mut(event.entity)
.expect("Called goto on an entity that's not in the world"); .expect("Called goto on an entity that's not in the world");
let start = Node { let start = BlockPos::from(position);
pos: BlockPos::from(position),
vertical_vel: VerticalVel::None,
};
let world_lock = instance_container let world_lock = instance_container
.get(world_name) .get(world_name)
@ -138,64 +138,60 @@ fn goto_listener(
let task = thread_pool.spawn(async move { let task = thread_pool.spawn(async move {
debug!("start: {start:?}, end: {end:?}"); debug!("start: {start:?}, end: {end:?}");
let possible_moves: Vec<&dyn moves::Move> = vec![ let possible_moves: Vec<DefaultMoves> = vec![
&moves::ForwardMove(CardinalDirection::North), DefaultMoves::Forward(CardinalDirection::North),
&moves::ForwardMove(CardinalDirection::East), DefaultMoves::Forward(CardinalDirection::East),
&moves::ForwardMove(CardinalDirection::South), DefaultMoves::Forward(CardinalDirection::South),
&moves::ForwardMove(CardinalDirection::West), DefaultMoves::Forward(CardinalDirection::West),
// //
&moves::AscendMove(CardinalDirection::North), DefaultMoves::Ascend(CardinalDirection::North),
&moves::AscendMove(CardinalDirection::East), DefaultMoves::Ascend(CardinalDirection::East),
&moves::AscendMove(CardinalDirection::South), DefaultMoves::Ascend(CardinalDirection::South),
&moves::AscendMove(CardinalDirection::West), DefaultMoves::Ascend(CardinalDirection::West),
// //
&moves::DescendMove(CardinalDirection::North), DefaultMoves::Descend(CardinalDirection::North),
&moves::DescendMove(CardinalDirection::East), DefaultMoves::Descend(CardinalDirection::East),
&moves::DescendMove(CardinalDirection::South), DefaultMoves::Descend(CardinalDirection::South),
&moves::DescendMove(CardinalDirection::West), DefaultMoves::Descend(CardinalDirection::West),
// //
&moves::DiagonalMove(CardinalDirection::North), DefaultMoves::Diagonal(CardinalDirection::North),
&moves::DiagonalMove(CardinalDirection::East), DefaultMoves::Diagonal(CardinalDirection::East),
&moves::DiagonalMove(CardinalDirection::South), DefaultMoves::Diagonal(CardinalDirection::South),
&moves::DiagonalMove(CardinalDirection::West), DefaultMoves::Diagonal(CardinalDirection::West),
]; ];
let successors = |node: &Node| { let successors = |pos: BlockPos| {
let mut edges = Vec::new(); let mut edges = Vec::new();
let world = world_lock.read(); let world = world_lock.read();
for possible_move in &possible_moves { for possible_move in &possible_moves {
let possible_move = possible_move.get(&world, node); let move_result = possible_move.get(&world, pos);
if let Some(possible_move) = possible_move { if let Some(edge) = move_result {
edges.push(Edge { edges.push(edge);
target: possible_move.node,
cost: possible_move.cost,
});
} }
} }
edges edges
}; };
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let p = a_star( let astar::Path { movements, partial } = a_star(
start, start,
|n| goal.heuristic(n), |n| goal.heuristic(n),
successors, successors,
|n| goal.success(n), |n| goal.success(n),
Duration::from_secs(1),
); );
let end_time = std::time::Instant::now(); let end_time = std::time::Instant::now();
debug!("path: {p:?}"); debug!("movements: {movements:?}");
debug!("partial: {partial:?}");
debug!("time: {:?}", end_time - start_time); debug!("time: {:?}", end_time - start_time);
// convert the Option<Vec<Node>> to a VecDeque<Node> let path = movements.into_iter().collect::<VecDeque<_>>();
if let Some(p) = p { Some(PathFoundEvent {
let path = p.into_iter().collect::<VecDeque<_>>(); entity,
// commands.entity(event.entity).insert(Pathfinder { path: p }); path: Some(path),
Some(PathFoundEvent { entity, path }) })
} else {
error!("no path found");
None
}
}); });
commands.spawn(ComputePath(task)); commands.spawn(ComputePath(task));
@ -226,7 +222,12 @@ fn path_found_listener(mut events: EventReader<PathFoundEvent>, mut query: Query
let mut pathfinder = query let mut pathfinder = query
.get_mut(event.entity) .get_mut(event.entity)
.expect("Path found for an entity that doesn't have a pathfinder"); .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 { for (entity, mut pathfinder, position, physics) in &mut query {
loop { loop {
let Some(target) = pathfinder.path.front() else { let Some(movement) = pathfinder.path.front() else {
break; break;
}; };
let center = target.pos.center(); // we check if the goal was reached *before* actually executing the movement so
// println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos()); // we don't unnecessarily execute a movement when it wasn't necessary
look_at_events.send(LookAtEvent { if is_goal_reached(movement.target, position, physics) {
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) {
// println!("reached target"); // println!("reached target");
pathfinder.path.pop_front(); pathfinder.path.pop_front();
if pathfinder.path.is_empty() { if pathfinder.path.is_empty() {
@ -273,9 +256,21 @@ fn tick_execute_path(
}); });
} }
// tick again, maybe we already reached the next node! // tick again, maybe we already reached the next node!
} else { continue;
break;
} }
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,49 +291,26 @@ 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 { pub trait Goal {
fn heuristic(&self, n: &Node) -> f32; fn heuristic(&self, n: BlockPos) -> f32;
fn success(&self, n: &Node) -> bool; fn success(&self, n: BlockPos) -> bool;
// TODO: this should be removed and mtdstarlite should stop depending on // TODO: this should be removed and mtdstarlite should stop depending on
// being given a goal node // 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
/// Returns whether the entity is at the node and should start going to the /// next node.
/// next node. #[must_use]
#[must_use] pub fn is_goal_reached(goal_pos: BlockPos, current_pos: &Position, physics: &Physics) -> bool {
pub fn is_reached(&self, position: &Position, physics: &Physics) -> bool { // println!(
// println!( // "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}",
// "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}", // entity.delta.y,
// entity.delta.y, // BlockPos::from(entity.pos()),
// BlockPos::from(entity.pos()), // self.pos,
// self.pos, // self.vertical_vel
// self.vertical_vel // );
// ); BlockPos::from(current_pos) == goal_pos && physics.on_ground
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,
}
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,11 +1,31 @@
use super::{Node, VerticalVel}; use crate::{JumpEvent, LookAtEvent};
use azalea_core::{BlockPos, CardinalDirection};
use super::astar;
use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent};
use azalea_core::{BlockPos, CardinalDirection, Vec3};
use azalea_physics::collision::{self, BlockWithShape}; use azalea_physics::collision::{self, BlockWithShape};
use azalea_world::Instance; 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 /// whether this block is passable
fn is_block_passable(pos: &BlockPos, world: &Instance) -> bool { fn is_block_passable(pos: &BlockPos, world: &Instance) -> bool {
if let Some(block) = world.chunks.get_block_state(pos) { 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() block.shape() == &collision::empty_shape()
} else { } else {
false 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) /// whether this block has a solid hitbox (i.e. we can stand on it)
fn is_block_solid(pos: &BlockPos, world: &Instance) -> bool { fn is_block_solid(pos: &BlockPos, world: &Instance) -> bool {
if let Some(block) = world.chunks.get_block_state(pos) { if let Some(block) = world.chunks.get_block_state(pos) {
// block.shape() == &collision::block_shape() block.shape() == &collision::block_shape()
block.shape() != &collision::empty_shape()
} else { } else {
false false
} }
@ -53,62 +72,148 @@ const JUMP_COST: f32 = 0.5;
const WALK_ONE_BLOCK_COST: f32 = 1.0; const WALK_ONE_BLOCK_COST: f32 = 1.0;
const FALL_ONE_BLOCK_COST: f32 = 0.5; const FALL_ONE_BLOCK_COST: f32 = 0.5;
pub trait Move: Send + Sync { #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult>; pub enum DefaultMoves {
Forward(CardinalDirection),
Ascend(CardinalDirection),
Descend(CardinalDirection),
Diagonal(CardinalDirection),
} }
pub struct MoveResult {
pub node: Node, impl DefaultMoves {
pub cost: f32, 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); pub struct ForwardMove(pub CardinalDirection);
impl Move for ForwardMove { impl MoveImpl for ForwardMove {
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> { fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
let offset = BlockPos::new(self.0.x(), 0, self.0.z()); 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; return None;
} }
let cost = WALK_ONE_BLOCK_COST; let cost = WALK_ONE_BLOCK_COST;
Some(MoveResult { Some(Edge {
node: Node { movement: astar::Movement {
pos: node.pos + offset, target: pos + offset,
vertical_vel: VerticalVel::None, data: MoveData {
move_kind: DefaultMoves::Forward(self.0),
},
}, },
cost, 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); pub struct AscendMove(pub CardinalDirection);
impl Move for AscendMove { impl MoveImpl for AscendMove {
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> { fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
let offset = BlockPos::new(self.0.x(), 1, self.0.z()); let offset = BlockPos::new(self.0.x(), 1, self.0.z());
if node.vertical_vel != VerticalVel::None if !is_block_passable(&pos.up(2), world) || !is_standable(&(pos + offset), world) {
|| !is_block_passable(&node.pos.up(2), world)
|| !is_standable(&(node.pos + offset), world)
{
return None; return None;
} }
let cost = WALK_ONE_BLOCK_COST + JUMP_COST; let cost = WALK_ONE_BLOCK_COST + JUMP_COST;
Some(MoveResult { // Some(MoveResult {
node: Node { // node: Node {
pos: node.pos + offset, // pos: node.pos + offset,
vertical_vel: VerticalVel::None, // vertical_vel: VerticalVel::None,
// },
// cost,
// })
Some(Edge {
movement: astar::Movement {
target: pos + offset,
data: MoveData {
move_kind: DefaultMoves::Ascend(self.0),
},
}, },
cost, 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); pub struct DescendMove(pub CardinalDirection);
impl Move for DescendMove { impl MoveImpl for DescendMove {
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> { fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
let new_horizontal_position = node.pos + BlockPos::new(self.0.x(), 0, self.0.z()); let new_horizontal_position = pos + BlockPos::new(self.0.x(), 0, self.0.z());
let fall_distance = fall_distance(&new_horizontal_position, world); let fall_distance = fall_distance(&new_horizontal_position, world);
if fall_distance == 0 { if fall_distance == 0 {
return None; return None;
@ -119,57 +224,104 @@ impl Move for DescendMove {
let new_position = new_horizontal_position.down(fall_distance as i32); let new_position = new_horizontal_position.down(fall_distance as i32);
// check whether 3 blocks vertically forward are passable // 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; return None;
} }
let cost = WALK_ONE_BLOCK_COST + FALL_ONE_BLOCK_COST * fall_distance as f32; let cost = WALK_ONE_BLOCK_COST + FALL_ONE_BLOCK_COST * fall_distance as f32;
Some(MoveResult { Some(Edge {
node: Node { movement: astar::Movement {
pos: new_position, target: new_position,
vertical_vel: VerticalVel::None, data: MoveData {
move_kind: DefaultMoves::Descend(self.0),
},
}, },
cost, 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); pub struct DiagonalMove(pub CardinalDirection);
impl Move for DiagonalMove { impl MoveImpl for DiagonalMove {
fn get(&self, world: &Instance, node: &Node) -> Option<MoveResult> { fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
if node.vertical_vel != VerticalVel::None {
return None;
}
let right = self.0.right(); let right = self.0.right();
let offset = BlockPos::new(self.0.x() + right.x(), 0, self.0.z() + right.z()); let offset = BlockPos::new(self.0.x() + right.x(), 0, self.0.z() + right.z());
if !is_passable( 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, world,
) && !is_passable( ) && !is_passable(
&BlockPos::new( &BlockPos::new(
node.pos.x + self.0.right().x(), pos.x + self.0.right().x(),
node.pos.y, pos.y,
node.pos.z + self.0.right().z(), pos.z + self.0.right().z(),
), ),
world, world,
) { ) {
return None; return None;
} }
if !is_standable(&(node.pos + offset), world) { if !is_standable(&(pos + offset), world) {
return None; return None;
} }
let cost = WALK_ONE_BLOCK_COST * 1.4; let cost = WALK_ONE_BLOCK_COST * 1.4;
Some(MoveResult { // Some(MoveResult {
node: Node { // node: Node {
pos: node.pos + offset, // pos: node.pos + offset,
vertical_vel: VerticalVel::None, // vertical_vel: VerticalVel::None,
// },
// cost,
// })
Some(Edge {
movement: astar::Movement {
target: pos + offset,
data: MoveData {
move_kind: DefaultMoves::Diagonal(self.0),
},
}, },
cost, 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)] #[cfg(test)]