aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xazalea-core/src/direction.rs2
-rw-r--r--azalea/src/bot.rs10
-rw-r--r--azalea/src/pathfinder/astar.rs152
-rw-r--r--azalea/src/pathfinder/goals.rs21
-rw-r--r--azalea/src/pathfinder/mod.rs184
-rw-r--r--azalea/src/pathfinder/moves.rs250
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<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;
}
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<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(&current_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(&current_node)
.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 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<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
- 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(&current) {
+ while let Some(node) = nodes.remove(&current) {
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<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,
}
-pub struct Edge<N: Eq + Hash + Copy, W: PartialOrd + Copy> {
- pub target: N,
- pub cost: W,
+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))
}
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<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,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<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)]