From 5d7669f72b02c749a02bf034d382028e62509540 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 26 Aug 2023 03:08:35 -0500 Subject: [PATCH] simplify pathfinder --- azalea-core/src/direction.rs | 2 +- azalea/src/bot.rs | 10 +- azalea/src/pathfinder/astar.rs | 152 ++++++++++++++------ azalea/src/pathfinder/goals.rs | 21 ++- azalea/src/pathfinder/mod.rs | 184 ++++++++++-------------- azalea/src/pathfinder/moves.rs | 250 ++++++++++++++++++++++++++------- 6 files changed, 405 insertions(+), 214 deletions(-) diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs index c872f26c..d3a0e4a9 100755 --- a/azalea-core/src/direction.rs +++ b/azalea-core/src/direction.rs @@ -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, diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 10bf1161..3f56555a 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -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, ) { 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; } diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index 0bdd0f17..ed39510f 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -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( - start: N, +pub struct Path +where + P: Eq + Hash + Copy + Debug, +{ + pub movements: Vec>, + pub partial: bool, +} + +pub fn a_star( + start: P, heuristic: HeuristicFn, successors: SuccessorsFn, success: SuccessFn, -) -> Option> + timeout: Duration, +) -> Path where - N: Eq + Hash + Copy + Debug, - W: PartialOrd + Default + Copy + num_traits::Bounded + Debug + Add, - HeuristicFn: Fn(&N) -> W, - SuccessorsFn: Fn(&N) -> Vec>, - SuccessFn: Fn(&N) -> bool, + P: Eq + Hash + Copy + Debug, + HeuristicFn: Fn(P) -> f32, + SuccessorsFn: Fn(P) -> Vec>, + 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> = HashMap::new(); + open_set.push(start, Reverse(Weight(0.))); + let mut nodes: HashMap> = 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; + } } } + + 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(nodes: &HashMap>, current: N) -> Vec +fn reconstruct_path(mut nodes: HashMap>, current: P) -> Vec> 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 { - pub data: N, - pub came_from: Option, - pub g_score: W, - pub f_score: W, +pub struct Node { + pub position: P, + pub movement_data: Option, + pub came_from: Option

, + pub g_score: f32, + pub f_score: f32, } -pub struct Edge { - pub target: N, - pub cost: W, +pub struct Edge { + pub movement: Movement, + pub cost: f32, +} + +pub struct Movement { + pub target: P, + pub data: M, +} + +impl Debug for Movement { + 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 Clone for Movement { + fn clone(&self) -> Self { + Self { + target: self.target, + data: self.data.clone(), + } + } } #[derive(PartialEq)] -pub struct Weight(W); -impl Ord for Weight { +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 Eq for Weight {} -impl PartialOrd for Weight { +impl Eq for Weight {} +impl PartialOrd for Weight { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs index 95f1b74f..a76e0314 100644 --- a/azalea/src/pathfinder/goals.rs +++ b/azalea/src/pathfinder/goals.rs @@ -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 success(&self, n: BlockPos) -> bool { + n == self.pos } - fn goal_node(&self) -> Node { - Node { - pos: self.pos, - vertical_vel: VerticalVel::None, - } + fn goal_node(&self) -> BlockPos { + self.pos } } diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 03a75599..f6971155 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -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, + pub path: VecDeque>, } #[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, + pub path: Option>>, } #[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 = 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> to a VecDeque - if let Some(p) = p { - let path = p.into_iter().collect::>(); - // commands.entity(event.entity).insert(Pathfinder { path: p }); - Some(PathFoundEvent { entity, path }) - } else { - error!("no path found"); - None - } + let path = movements.into_iter().collect::>(); + Some(PathFoundEvent { + entity, + path: Some(path), + }) }); commands.spawn(ComputePath(task)); @@ -226,7 +222,12 @@ fn path_found_listener(mut events: EventReader, 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,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 { - 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 { - // println!( - // "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}", - // entity.delta.y, - // BlockPos::from(entity.pos()), - // 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, - } - } +/// Returns whether the entity is at the node and should start going to the +/// next node. +#[must_use] +pub fn is_goal_reached(goal_pos: BlockPos, current_pos: &Position, physics: &Physics) -> bool { + // println!( + // "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}", + // entity.delta.y, + // BlockPos::from(entity.pos()), + // self.pos, + // self.vertical_vel + // ); + BlockPos::from(current_pos) == goal_pos && physics.on_ground } #[cfg(test)] diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs index 0cc211ac..787d6246 100644 --- a/azalea/src/pathfinder/moves.rs +++ b/azalea/src/pathfinder/moves.rs @@ -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; + +#[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; +#[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 { + 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; + 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 { +impl MoveImpl for ForwardMove { + fn get(&self, world: &Instance, pos: BlockPos) -> Option { 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 { +impl MoveImpl for AscendMove { + fn get(&self, world: &Instance, pos: BlockPos) -> Option { 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 { - 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 { + 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 { - if node.vertical_vel != VerticalVel::None { - return None; - } - +impl MoveImpl for DiagonalMove { + fn get(&self, world: &Instance, pos: BlockPos) -> Option { 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)]