aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2023-08-26 18:08:13 -0500
committermat <git@matdoes.dev>2023-08-26 18:08:13 -0500
commitdea717b68e2945777c68d44ce321639cf09ea226 (patch)
treef3909a036730343def45978f0d59f0a82673b1c2
parent5d7669f72b02c749a02bf034d382028e62509540 (diff)
downloadazalea-drasl-dea717b68e2945777c68d44ce321639cf09ea226.tar.xz
simplify pathfinder more
-rw-r--r--azalea/src/pathfinder/mod.rs43
-rw-r--r--azalea/src/pathfinder/moves.rs420
-rw-r--r--azalea/src/pathfinder/moves/basic.rs209
-rw-r--r--azalea/src/pathfinder/moves/mod.rs182
4 files changed, 397 insertions, 457 deletions
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index f6971155..76d901c3 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -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;
}
}
diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs
deleted file mode 100644
index 787d6246..00000000
--- a/azalea/src/pathfinder/moves.rs
+++ /dev/null
@@ -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(&current_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));
- }
-}
diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs
new file mode 100644
index 00000000..2d0c47c2
--- /dev/null
+++ b/azalea/src/pathfinder/moves/basic.rs
@@ -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,
+ });
+}
diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs
new file mode 100644
index 00000000..63b74945
--- /dev/null
+++ b/azalea/src/pathfinder/moves/mod.rs
@@ -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(&current_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));
+ }
+}