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::pathfinder::astar::a_star;
|
||||
use crate::pathfinder::moves::DefaultMoves;
|
||||
use crate::WalkDirection;
|
||||
|
||||
use crate::app::{App, Plugin};
|
||||
|
@ -18,7 +17,7 @@ use crate::ecs::{
|
|||
};
|
||||
use azalea_client::movement::walk_listener;
|
||||
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||
use azalea_core::{BlockPos, CardinalDirection};
|
||||
use azalea_core::BlockPos;
|
||||
use azalea_entity::metadata::Player;
|
||||
use azalea_entity::Local;
|
||||
use azalea_entity::{Physics, Position};
|
||||
|
@ -119,6 +118,8 @@ fn goto_listener(
|
|||
mut query: Query<(&Position, &InstanceName)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
let successors_fn = moves::basic::basic_move;
|
||||
|
||||
let thread_pool = AsyncComputeTaskPool::get();
|
||||
|
||||
for event in events.iter() {
|
||||
|
@ -138,40 +139,9 @@ fn goto_listener(
|
|||
let task = thread_pool.spawn(async move {
|
||||
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 mut edges = Vec::new();
|
||||
|
||||
let world = world_lock.read();
|
||||
for possible_move in &possible_moves {
|
||||
let move_result = possible_move.get(&world, pos);
|
||||
if let Some(edge) = move_result {
|
||||
edges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
edges
|
||||
successors_fn(&world, pos)
|
||||
};
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
|
@ -183,7 +153,6 @@ fn goto_listener(
|
|||
Duration::from_secs(1),
|
||||
);
|
||||
let end_time = std::time::Instant::now();
|
||||
debug!("movements: {movements:?}");
|
||||
debug!("partial: {partial:?}");
|
||||
debug!("time: {:?}", end_time - start_time);
|
||||
|
||||
|
@ -268,8 +237,8 @@ fn tick_execute_path(
|
|||
walk_events: &mut walk_events,
|
||||
jump_events: &mut jump_events,
|
||||
};
|
||||
trace!("executing move {:?}", movement.data.move_kind);
|
||||
movement.data.move_kind.execute(ctx);
|
||||
trace!("executing move");
|
||||
(movement.data.execute)(ctx);
|
||||
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