mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 22:52:32 +00:00
simplify pathfinder more
This commit is contained in:
parent
5d7669f72b
commit
dea717b68e
4 changed files with 397 additions and 457 deletions
|
@ -5,7 +5,6 @@ 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::pathfinder::moves::DefaultMoves;
|
|
||||||
use crate::WalkDirection;
|
use crate::WalkDirection;
|
||||||
|
|
||||||
use crate::app::{App, Plugin};
|
use crate::app::{App, Plugin};
|
||||||
|
@ -18,7 +17,7 @@ use crate::ecs::{
|
||||||
};
|
};
|
||||||
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;
|
||||||
use azalea_entity::metadata::Player;
|
use azalea_entity::metadata::Player;
|
||||||
use azalea_entity::Local;
|
use azalea_entity::Local;
|
||||||
use azalea_entity::{Physics, Position};
|
use azalea_entity::{Physics, Position};
|
||||||
|
@ -119,6 +118,8 @@ fn goto_listener(
|
||||||
mut query: Query<(&Position, &InstanceName)>,
|
mut query: Query<(&Position, &InstanceName)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
|
let successors_fn = moves::basic::basic_move;
|
||||||
|
|
||||||
let thread_pool = AsyncComputeTaskPool::get();
|
let thread_pool = AsyncComputeTaskPool::get();
|
||||||
|
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
|
@ -138,40 +139,9 @@ 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<DefaultMoves> = vec![
|
|
||||||
DefaultMoves::Forward(CardinalDirection::North),
|
|
||||||
DefaultMoves::Forward(CardinalDirection::East),
|
|
||||||
DefaultMoves::Forward(CardinalDirection::South),
|
|
||||||
DefaultMoves::Forward(CardinalDirection::West),
|
|
||||||
//
|
|
||||||
DefaultMoves::Ascend(CardinalDirection::North),
|
|
||||||
DefaultMoves::Ascend(CardinalDirection::East),
|
|
||||||
DefaultMoves::Ascend(CardinalDirection::South),
|
|
||||||
DefaultMoves::Ascend(CardinalDirection::West),
|
|
||||||
//
|
|
||||||
DefaultMoves::Descend(CardinalDirection::North),
|
|
||||||
DefaultMoves::Descend(CardinalDirection::East),
|
|
||||||
DefaultMoves::Descend(CardinalDirection::South),
|
|
||||||
DefaultMoves::Descend(CardinalDirection::West),
|
|
||||||
//
|
|
||||||
DefaultMoves::Diagonal(CardinalDirection::North),
|
|
||||||
DefaultMoves::Diagonal(CardinalDirection::East),
|
|
||||||
DefaultMoves::Diagonal(CardinalDirection::South),
|
|
||||||
DefaultMoves::Diagonal(CardinalDirection::West),
|
|
||||||
];
|
|
||||||
|
|
||||||
let successors = |pos: BlockPos| {
|
let successors = |pos: BlockPos| {
|
||||||
let mut edges = Vec::new();
|
|
||||||
|
|
||||||
let world = world_lock.read();
|
let world = world_lock.read();
|
||||||
for possible_move in &possible_moves {
|
successors_fn(&world, pos)
|
||||||
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 start_time = std::time::Instant::now();
|
||||||
|
@ -183,7 +153,6 @@ fn goto_listener(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
);
|
);
|
||||||
let end_time = std::time::Instant::now();
|
let end_time = std::time::Instant::now();
|
||||||
debug!("movements: {movements:?}");
|
|
||||||
debug!("partial: {partial:?}");
|
debug!("partial: {partial:?}");
|
||||||
debug!("time: {:?}", end_time - start_time);
|
debug!("time: {:?}", end_time - start_time);
|
||||||
|
|
||||||
|
@ -268,8 +237,8 @@ fn tick_execute_path(
|
||||||
walk_events: &mut walk_events,
|
walk_events: &mut walk_events,
|
||||||
jump_events: &mut jump_events,
|
jump_events: &mut jump_events,
|
||||||
};
|
};
|
||||||
trace!("executing move {:?}", movement.data.move_kind);
|
trace!("executing move");
|
||||||
movement.data.move_kind.execute(ctx);
|
(movement.data.execute)(ctx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,420 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this block and the block above are passable
|
|
||||||
fn is_passable(pos: &BlockPos, world: &Instance) -> bool {
|
|
||||||
is_block_passable(pos, world) && is_block_passable(&pos.up(1), world)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether we can stand in this position. Checks if the block below is solid,
|
|
||||||
/// and that the two blocks above that are passable.
|
|
||||||
|
|
||||||
fn is_standable(pos: &BlockPos, world: &Instance) -> bool {
|
|
||||||
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the amount of air blocks until the next solid block below this one.
|
|
||||||
fn fall_distance(pos: &BlockPos, world: &Instance) -> u32 {
|
|
||||||
let mut distance = 0;
|
|
||||||
let mut current_pos = pos.down(1);
|
|
||||||
while is_block_passable(¤t_pos, world) {
|
|
||||||
distance += 1;
|
|
||||||
current_pos = current_pos.down(1);
|
|
||||||
|
|
||||||
if current_pos.y < world.chunks.min_y {
|
|
||||||
return u32::MAX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
distance
|
|
||||||
}
|
|
||||||
|
|
||||||
const JUMP_COST: f32 = 0.5;
|
|
||||||
const WALK_ONE_BLOCK_COST: f32 = 1.0;
|
|
||||||
const FALL_ONE_BLOCK_COST: f32 = 0.5;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub enum DefaultMoves {
|
|
||||||
Forward(CardinalDirection),
|
|
||||||
Ascend(CardinalDirection),
|
|
||||||
Descend(CardinalDirection),
|
|
||||||
Diagonal(CardinalDirection),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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(&(pos + offset), world) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cost = WALK_ONE_BLOCK_COST;
|
|
||||||
|
|
||||||
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 MoveImpl for AscendMove {
|
|
||||||
fn get(&self, world: &Instance, pos: BlockPos) -> Option<Edge> {
|
|
||||||
let offset = BlockPos::new(self.0.x(), 1, self.0.z());
|
|
||||||
|
|
||||||
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,
|
|
||||||
// },
|
|
||||||
// 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 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;
|
|
||||||
}
|
|
||||||
if fall_distance > 3 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let new_position = new_horizontal_position.down(fall_distance as i32);
|
|
||||||
|
|
||||||
// check whether 3 blocks vertically forward are passable
|
|
||||||
if !is_passable(&new_horizontal_position, world) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cost = WALK_ONE_BLOCK_COST + FALL_ONE_BLOCK_COST * fall_distance as f32;
|
|
||||||
|
|
||||||
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 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(pos.x + self.0.x(), pos.y, pos.z + self.0.z()),
|
|
||||||
world,
|
|
||||||
) && !is_passable(
|
|
||||||
&BlockPos::new(
|
|
||||||
pos.x + self.0.right().x(),
|
|
||||||
pos.y,
|
|
||||||
pos.z + self.0.right().z(),
|
|
||||||
),
|
|
||||||
world,
|
|
||||||
) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
// },
|
|
||||||
// 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)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use azalea_block::BlockState;
|
|
||||||
use azalea_core::ChunkPos;
|
|
||||||
use azalea_world::{Chunk, ChunkStorage, PartialInstance};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_passable() {
|
|
||||||
let mut partial_world = PartialInstance::default();
|
|
||||||
let mut chunk_storage = ChunkStorage::default();
|
|
||||||
|
|
||||||
partial_world.chunks.set(
|
|
||||||
&ChunkPos { x: 0, z: 0 },
|
|
||||||
Some(Chunk::default()),
|
|
||||||
&mut chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 0, 0),
|
|
||||||
azalea_registry::Block::Stone.into(),
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 1, 0),
|
|
||||||
BlockState::AIR,
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
|
|
||||||
let world = chunk_storage.into();
|
|
||||||
assert!(!is_block_passable(&BlockPos::new(0, 0, 0), &world));
|
|
||||||
assert!(is_block_passable(&BlockPos::new(0, 1, 0), &world));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_solid() {
|
|
||||||
let mut partial_world = PartialInstance::default();
|
|
||||||
let mut chunk_storage = ChunkStorage::default();
|
|
||||||
partial_world.chunks.set(
|
|
||||||
&ChunkPos { x: 0, z: 0 },
|
|
||||||
Some(Chunk::default()),
|
|
||||||
&mut chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 0, 0),
|
|
||||||
azalea_registry::Block::Stone.into(),
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 1, 0),
|
|
||||||
BlockState::AIR,
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
|
|
||||||
let world = chunk_storage.into();
|
|
||||||
assert!(is_block_solid(&BlockPos::new(0, 0, 0), &world));
|
|
||||||
assert!(!is_block_solid(&BlockPos::new(0, 1, 0), &world));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_standable() {
|
|
||||||
let mut partial_world = PartialInstance::default();
|
|
||||||
let mut chunk_storage = ChunkStorage::default();
|
|
||||||
partial_world.chunks.set(
|
|
||||||
&ChunkPos { x: 0, z: 0 },
|
|
||||||
Some(Chunk::default()),
|
|
||||||
&mut chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 0, 0),
|
|
||||||
azalea_registry::Block::Stone.into(),
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 1, 0),
|
|
||||||
BlockState::AIR,
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 2, 0),
|
|
||||||
BlockState::AIR,
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
partial_world.chunks.set_block_state(
|
|
||||||
&BlockPos::new(0, 3, 0),
|
|
||||||
BlockState::AIR,
|
|
||||||
&chunk_storage,
|
|
||||||
);
|
|
||||||
|
|
||||||
let world = chunk_storage.into();
|
|
||||||
assert!(is_standable(&BlockPos::new(0, 1, 0), &world));
|
|
||||||
assert!(!is_standable(&BlockPos::new(0, 0, 0), &world));
|
|
||||||
assert!(!is_standable(&BlockPos::new(0, 2, 0), &world));
|
|
||||||
}
|
|
||||||
}
|
|
209
azalea/src/pathfinder/moves/basic.rs
Normal file
209
azalea/src/pathfinder/moves/basic.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use azalea_client::{SprintDirection, StartSprintEvent};
|
||||||
|
use azalea_core::{BlockPos, CardinalDirection};
|
||||||
|
use azalea_world::Instance;
|
||||||
|
|
||||||
|
use crate::{pathfinder::astar, JumpEvent, LookAtEvent};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
fall_distance, is_block_passable, is_passable, is_standable, Edge, ExecuteCtx, MoveData,
|
||||||
|
FALL_ONE_BLOCK_COST, JUMP_COST, WALK_ONE_BLOCK_COST,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn basic_move(world: &Instance, node: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
edges.extend(forward_move(world, node));
|
||||||
|
edges.extend(ascend_move(world, node));
|
||||||
|
edges.extend(descend_move(world, node));
|
||||||
|
edges.extend(diagonal_move(world, node));
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let offset = BlockPos::new(dir.x(), 0, dir.z());
|
||||||
|
|
||||||
|
if !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = WALK_ONE_BLOCK_COST;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_forward_move,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_forward_move(
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ascend_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let offset = BlockPos::new(dir.x(), 1, dir.z());
|
||||||
|
|
||||||
|
if !is_block_passable(&pos.up(2), world) || !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = WALK_ONE_BLOCK_COST + JUMP_COST;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_ascend_move,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
fn execute_ascend_move(
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn descend_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let new_horizontal_position = pos + BlockPos::new(dir.x(), 0, dir.z());
|
||||||
|
let fall_distance = fall_distance(&new_horizontal_position, world);
|
||||||
|
if fall_distance == 0 || fall_distance > 3 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let new_position = new_horizontal_position.down(fall_distance as i32);
|
||||||
|
|
||||||
|
// check whether 3 blocks vertically forward are passable
|
||||||
|
if !is_passable(&new_horizontal_position, world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cost = WALK_ONE_BLOCK_COST + FALL_ONE_BLOCK_COST * fall_distance as f32;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: new_position,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_descend_move,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
fn execute_descend_move(
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagonal_move(world: &Instance, pos: BlockPos) -> Vec<Edge> {
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for dir in CardinalDirection::iter() {
|
||||||
|
let right = dir.right();
|
||||||
|
let offset = BlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
|
||||||
|
|
||||||
|
if !is_passable(
|
||||||
|
&BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z()),
|
||||||
|
world,
|
||||||
|
) && !is_passable(
|
||||||
|
&BlockPos::new(pos.x + dir.right().x(), pos.y, pos.z + dir.right().z()),
|
||||||
|
world,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !is_standable(&(pos + offset), world) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cost = WALK_ONE_BLOCK_COST * 1.4;
|
||||||
|
|
||||||
|
edges.push(Edge {
|
||||||
|
movement: astar::Movement {
|
||||||
|
target: pos + offset,
|
||||||
|
data: MoveData {
|
||||||
|
execute: &execute_diagonal_move,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
fn execute_diagonal_move(
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
182
azalea/src/pathfinder/moves/mod.rs
Normal file
182
azalea/src/pathfinder/moves/mod.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
pub mod basic;
|
||||||
|
|
||||||
|
use crate::{JumpEvent, LookAtEvent};
|
||||||
|
|
||||||
|
use super::astar;
|
||||||
|
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||||
|
use azalea_core::{BlockPos, 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(Clone)]
|
||||||
|
pub struct MoveData {
|
||||||
|
// pub move_kind: BasicMoves,
|
||||||
|
pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this block and the block above are passable
|
||||||
|
fn is_passable(pos: &BlockPos, world: &Instance) -> bool {
|
||||||
|
is_block_passable(pos, world) && is_block_passable(&pos.up(1), world)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether we can stand in this position. Checks if the block below is solid,
|
||||||
|
/// and that the two blocks above that are passable.
|
||||||
|
|
||||||
|
fn is_standable(pos: &BlockPos, world: &Instance) -> bool {
|
||||||
|
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the amount of air blocks until the next solid block below this one.
|
||||||
|
fn fall_distance(pos: &BlockPos, world: &Instance) -> u32 {
|
||||||
|
let mut distance = 0;
|
||||||
|
let mut current_pos = pos.down(1);
|
||||||
|
while is_block_passable(¤t_pos, world) {
|
||||||
|
distance += 1;
|
||||||
|
current_pos = current_pos.down(1);
|
||||||
|
|
||||||
|
if current_pos.y < world.chunks.min_y {
|
||||||
|
return u32::MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
distance
|
||||||
|
}
|
||||||
|
|
||||||
|
const JUMP_COST: f32 = 0.5;
|
||||||
|
const WALK_ONE_BLOCK_COST: f32 = 1.0;
|
||||||
|
const FALL_ONE_BLOCK_COST: f32 = 0.5;
|
||||||
|
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use azalea_block::BlockState;
|
||||||
|
use azalea_core::ChunkPos;
|
||||||
|
use azalea_world::{Chunk, ChunkStorage, PartialInstance};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_passable() {
|
||||||
|
let mut partial_world = PartialInstance::default();
|
||||||
|
let mut chunk_storage = ChunkStorage::default();
|
||||||
|
|
||||||
|
partial_world.chunks.set(
|
||||||
|
&ChunkPos { x: 0, z: 0 },
|
||||||
|
Some(Chunk::default()),
|
||||||
|
&mut chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 0, 0),
|
||||||
|
azalea_registry::Block::Stone.into(),
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 1, 0),
|
||||||
|
BlockState::AIR,
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
|
||||||
|
let world = chunk_storage.into();
|
||||||
|
assert!(!is_block_passable(&BlockPos::new(0, 0, 0), &world));
|
||||||
|
assert!(is_block_passable(&BlockPos::new(0, 1, 0), &world));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_solid() {
|
||||||
|
let mut partial_world = PartialInstance::default();
|
||||||
|
let mut chunk_storage = ChunkStorage::default();
|
||||||
|
partial_world.chunks.set(
|
||||||
|
&ChunkPos { x: 0, z: 0 },
|
||||||
|
Some(Chunk::default()),
|
||||||
|
&mut chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 0, 0),
|
||||||
|
azalea_registry::Block::Stone.into(),
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 1, 0),
|
||||||
|
BlockState::AIR,
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
|
||||||
|
let world = chunk_storage.into();
|
||||||
|
assert!(is_block_solid(&BlockPos::new(0, 0, 0), &world));
|
||||||
|
assert!(!is_block_solid(&BlockPos::new(0, 1, 0), &world));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_standable() {
|
||||||
|
let mut partial_world = PartialInstance::default();
|
||||||
|
let mut chunk_storage = ChunkStorage::default();
|
||||||
|
partial_world.chunks.set(
|
||||||
|
&ChunkPos { x: 0, z: 0 },
|
||||||
|
Some(Chunk::default()),
|
||||||
|
&mut chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 0, 0),
|
||||||
|
azalea_registry::Block::Stone.into(),
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 1, 0),
|
||||||
|
BlockState::AIR,
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 2, 0),
|
||||||
|
BlockState::AIR,
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
partial_world.chunks.set_block_state(
|
||||||
|
&BlockPos::new(0, 3, 0),
|
||||||
|
BlockState::AIR,
|
||||||
|
&chunk_storage,
|
||||||
|
);
|
||||||
|
|
||||||
|
let world = chunk_storage.into();
|
||||||
|
assert!(is_standable(&BlockPos::new(0, 1, 0), &world));
|
||||||
|
assert!(!is_standable(&BlockPos::new(0, 0, 0), &world));
|
||||||
|
assert!(!is_standable(&BlockPos::new(0, 2, 0), &world));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue