mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
optimize pathfinder
This commit is contained in:
parent
d67aa07c13
commit
0ee9ed50e3
11 changed files with 275 additions and 93 deletions
|
@ -40,7 +40,9 @@ pub enum ChatPacket {
|
||||||
|
|
||||||
macro_rules! regex {
|
macro_rules! regex {
|
||||||
($re:literal $(,)?) => {{
|
($re:literal $(,)?) => {{
|
||||||
std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap())
|
static RE: std::sync::LazyLock<regex::Regex> =
|
||||||
|
std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap());
|
||||||
|
&RE
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ pub enum AxisCycle {
|
||||||
|
|
||||||
impl CardinalDirection {
|
impl CardinalDirection {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn x(self) -> i32 {
|
pub fn x(self) -> i16 {
|
||||||
match self {
|
match self {
|
||||||
CardinalDirection::East => 1,
|
CardinalDirection::East => 1,
|
||||||
CardinalDirection::West => -1,
|
CardinalDirection::West => -1,
|
||||||
|
@ -96,7 +96,7 @@ impl CardinalDirection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn z(self) -> i32 {
|
pub fn z(self) -> i16 {
|
||||||
match self {
|
match self {
|
||||||
CardinalDirection::South => 1,
|
CardinalDirection::South => 1,
|
||||||
CardinalDirection::North => -1,
|
CardinalDirection::North => -1,
|
||||||
|
|
|
@ -30,7 +30,7 @@ bevy_app = { workspace = true }
|
||||||
bevy_ecs = { workspace = true }
|
bevy_ecs = { workspace = true }
|
||||||
bevy_log = { workspace = true }
|
bevy_log = { workspace = true }
|
||||||
bevy_tasks = { workspace = true, features = ["multi_threaded"] }
|
bevy_tasks = { workspace = true, features = ["multi_threaded"] }
|
||||||
#bevy_time = { workspace = true }
|
# bevy_time = { workspace = true }
|
||||||
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
futures-lite = { workspace = true }
|
futures-lite = { workspace = true }
|
||||||
|
|
|
@ -5,6 +5,7 @@ use azalea::{
|
||||||
astar::{self, a_star, PathfinderTimeout},
|
astar::{self, a_star, PathfinderTimeout},
|
||||||
goals::{BlockPosGoal, Goal},
|
goals::{BlockPosGoal, Goal},
|
||||||
mining::MiningCache,
|
mining::MiningCache,
|
||||||
|
rel_block_pos::RelBlockPos,
|
||||||
world::CachedWorld,
|
world::CachedWorld,
|
||||||
},
|
},
|
||||||
BlockPos,
|
BlockPos,
|
||||||
|
@ -124,21 +125,23 @@ fn run_pathfinder_benchmark(
|
||||||
|
|
||||||
let (world, start, end) = generate_world(&mut partial_chunks, 4);
|
let (world, start, end) = generate_world(&mut partial_chunks, 4);
|
||||||
|
|
||||||
|
let origin = start;
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.clone().into())));
|
let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.clone().into())), origin);
|
||||||
let mining_cache =
|
let mining_cache =
|
||||||
MiningCache::new(Some(Menu::Player(azalea_inventory::Player::default())));
|
MiningCache::new(Some(Menu::Player(azalea_inventory::Player::default())));
|
||||||
let goal = BlockPosGoal(end);
|
let goal = BlockPosGoal(end);
|
||||||
|
|
||||||
let successors = |pos: BlockPos| {
|
let successors = |pos: RelBlockPos| {
|
||||||
azalea::pathfinder::call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
|
azalea::pathfinder::call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
|
||||||
};
|
};
|
||||||
|
|
||||||
let astar::Path { movements, partial } = a_star(
|
let astar::Path { movements, partial } = a_star(
|
||||||
start,
|
RelBlockPos::get_origin(origin),
|
||||||
|n| goal.heuristic(n),
|
|n| goal.heuristic(n.apply(origin)),
|
||||||
successors,
|
successors,
|
||||||
|n| goal.success(n),
|
|n| goal.success(n.apply(origin)),
|
||||||
PathfinderTimeout::Time(Duration::MAX),
|
PathfinderTimeout::Time(Duration::MAX),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ where
|
||||||
num_nodes += 1;
|
num_nodes += 1;
|
||||||
if success(current_node) {
|
if success(current_node) {
|
||||||
debug!("Nodes considered: {num_nodes}");
|
debug!("Nodes considered: {num_nodes}");
|
||||||
|
|
||||||
return Path {
|
return Path {
|
||||||
movements: reconstruct_path(nodes, current_node),
|
movements: reconstruct_path(nodes, current_node),
|
||||||
partial: false,
|
partial: false,
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod debug;
|
||||||
pub mod goals;
|
pub mod goals;
|
||||||
pub mod mining;
|
pub mod mining;
|
||||||
pub mod moves;
|
pub mod moves;
|
||||||
|
pub mod rel_block_pos;
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ use bevy_ecs::schedule::IntoSystemConfigs;
|
||||||
use bevy_tasks::{AsyncComputeTaskPool, Task};
|
use bevy_tasks::{AsyncComputeTaskPool, Task};
|
||||||
use futures_lite::future;
|
use futures_lite::future;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use rel_block_pos::RelBlockPos;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use self::debug::debug_render_path_with_particles;
|
use self::debug::debug_render_path_with_particles;
|
||||||
|
@ -321,8 +323,9 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
|
||||||
|
|
||||||
let goto_id = opts.goto_id_atomic.fetch_add(1, atomic::Ordering::SeqCst) + 1;
|
let goto_id = opts.goto_id_atomic.fetch_add(1, atomic::Ordering::SeqCst) + 1;
|
||||||
|
|
||||||
let cached_world = CachedWorld::new(opts.world_lock);
|
let origin = opts.start;
|
||||||
let successors = |pos: BlockPos| {
|
let cached_world = CachedWorld::new(opts.world_lock, origin);
|
||||||
|
let successors = |pos: RelBlockPos| {
|
||||||
call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos)
|
call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -345,10 +348,10 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let astar::Path { movements, partial } = a_star(
|
let astar::Path { movements, partial } = a_star(
|
||||||
opts.start,
|
RelBlockPos::get_origin(origin),
|
||||||
|n| opts.goal.heuristic(n),
|
|n| opts.goal.heuristic(n.apply(origin)),
|
||||||
successors,
|
successors,
|
||||||
|n| opts.goal.success(n),
|
|n| opts.goal.success(n.apply(origin)),
|
||||||
timeout,
|
timeout,
|
||||||
);
|
);
|
||||||
let end_time = Instant::now();
|
let end_time = Instant::now();
|
||||||
|
@ -368,7 +371,7 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
|
||||||
|
|
||||||
debug!("Path:");
|
debug!("Path:");
|
||||||
for movement in &movements {
|
for movement in &movements {
|
||||||
debug!(" {}", movement.target);
|
debug!(" {}", movement.target.apply(origin));
|
||||||
}
|
}
|
||||||
|
|
||||||
path = movements.into_iter().collect::<VecDeque<_>>();
|
path = movements.into_iter().collect::<VecDeque<_>>();
|
||||||
|
@ -394,10 +397,19 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace the RelBlockPos types with BlockPos
|
||||||
|
let mapped_path = path
|
||||||
|
.into_iter()
|
||||||
|
.map(|movement| astar::Movement {
|
||||||
|
target: movement.target.apply(origin),
|
||||||
|
data: movement.data,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Some(PathFoundEvent {
|
Some(PathFoundEvent {
|
||||||
entity: opts.entity,
|
entity: opts.entity,
|
||||||
start: opts.start,
|
start: opts.start,
|
||||||
path: Some(path),
|
path: Some(mapped_path),
|
||||||
is_partial,
|
is_partial,
|
||||||
successors_fn: opts.successors_fn,
|
successors_fn: opts.successors_fn,
|
||||||
allow_mining: opts.allow_mining,
|
allow_mining: opts.allow_mining,
|
||||||
|
@ -448,21 +460,27 @@ pub fn path_found_listener(
|
||||||
let world_lock = instance_container
|
let world_lock = instance_container
|
||||||
.get(instance_name)
|
.get(instance_name)
|
||||||
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
||||||
|
let origin = event.start;
|
||||||
let successors_fn: moves::SuccessorsFn = event.successors_fn;
|
let successors_fn: moves::SuccessorsFn = event.successors_fn;
|
||||||
let cached_world = CachedWorld::new(world_lock);
|
let cached_world = CachedWorld::new(world_lock, origin);
|
||||||
let mining_cache = MiningCache::new(if event.allow_mining {
|
let mining_cache = MiningCache::new(if event.allow_mining {
|
||||||
Some(inventory.inventory_menu.clone())
|
Some(inventory.inventory_menu.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
let successors = |pos: BlockPos| {
|
let successors = |pos: RelBlockPos| {
|
||||||
call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
|
call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(first_node_of_new_path) = path.front() {
|
if let Some(first_node_of_new_path) = path.front() {
|
||||||
if successors(last_node_of_current_path.target)
|
let last_target_of_current_path =
|
||||||
|
RelBlockPos::from_origin(origin, last_node_of_current_path.target);
|
||||||
|
let first_target_of_new_path =
|
||||||
|
RelBlockPos::from_origin(origin, first_node_of_new_path.target);
|
||||||
|
|
||||||
|
if successors(last_target_of_current_path)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|edge| edge.movement.target == first_node_of_new_path.target)
|
.any(|edge| edge.movement.target == first_target_of_new_path)
|
||||||
{
|
{
|
||||||
debug!("combining old and new paths");
|
debug!("combining old and new paths");
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -655,17 +673,19 @@ pub fn check_for_path_obstruction(
|
||||||
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
||||||
|
|
||||||
// obstruction check (the path we're executing isn't possible anymore)
|
// obstruction check (the path we're executing isn't possible anymore)
|
||||||
let cached_world = CachedWorld::new(world_lock);
|
let origin = executing_path.last_reached_node;
|
||||||
|
let cached_world = CachedWorld::new(world_lock, origin);
|
||||||
let mining_cache = MiningCache::new(if pathfinder.allow_mining {
|
let mining_cache = MiningCache::new(if pathfinder.allow_mining {
|
||||||
Some(inventory.inventory_menu.clone())
|
Some(inventory.inventory_menu.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
let successors =
|
let successors =
|
||||||
|pos: BlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos);
|
|pos: RelBlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos);
|
||||||
|
|
||||||
if let Some(obstructed_index) = check_path_obstructed(
|
if let Some(obstructed_index) = check_path_obstructed(
|
||||||
executing_path.last_reached_node,
|
origin,
|
||||||
|
RelBlockPos::from_origin(origin, executing_path.last_reached_node),
|
||||||
&executing_path.path,
|
&executing_path.path,
|
||||||
successors,
|
successors,
|
||||||
) {
|
) {
|
||||||
|
@ -873,18 +893,21 @@ pub fn stop_pathfinding_on_instance_change(
|
||||||
/// Checks whether the path has been obstructed, and returns Some(index) if it
|
/// Checks whether the path has been obstructed, and returns Some(index) if it
|
||||||
/// has been. The index is of the first obstructed node.
|
/// has been. The index is of the first obstructed node.
|
||||||
pub fn check_path_obstructed<SuccessorsFn>(
|
pub fn check_path_obstructed<SuccessorsFn>(
|
||||||
mut current_position: BlockPos,
|
origin: BlockPos,
|
||||||
|
mut current_position: RelBlockPos,
|
||||||
path: &VecDeque<astar::Movement<BlockPos, moves::MoveData>>,
|
path: &VecDeque<astar::Movement<BlockPos, moves::MoveData>>,
|
||||||
successors_fn: SuccessorsFn,
|
successors_fn: SuccessorsFn,
|
||||||
) -> Option<usize>
|
) -> Option<usize>
|
||||||
where
|
where
|
||||||
SuccessorsFn: Fn(BlockPos) -> Vec<astar::Edge<BlockPos, moves::MoveData>>,
|
SuccessorsFn: Fn(RelBlockPos) -> Vec<astar::Edge<RelBlockPos, moves::MoveData>>,
|
||||||
{
|
{
|
||||||
for (i, movement) in path.iter().enumerate() {
|
for (i, movement) in path.iter().enumerate() {
|
||||||
|
let movement_target = RelBlockPos::from_origin(origin, movement.target);
|
||||||
|
|
||||||
let mut found_obstruction = false;
|
let mut found_obstruction = false;
|
||||||
for edge in successors_fn(current_position) {
|
for edge in successors_fn(current_position) {
|
||||||
if edge.movement.target == movement.target {
|
if edge.movement.target == movement_target {
|
||||||
current_position = movement.target;
|
current_position = movement_target;
|
||||||
found_obstruction = false;
|
found_obstruction = false;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
@ -903,8 +926,8 @@ pub fn call_successors_fn(
|
||||||
cached_world: &CachedWorld,
|
cached_world: &CachedWorld,
|
||||||
mining_cache: &MiningCache,
|
mining_cache: &MiningCache,
|
||||||
successors_fn: SuccessorsFn,
|
successors_fn: SuccessorsFn,
|
||||||
pos: BlockPos,
|
pos: RelBlockPos,
|
||||||
) -> Vec<astar::Edge<BlockPos, moves::MoveData>> {
|
) -> Vec<astar::Edge<RelBlockPos, moves::MoveData>> {
|
||||||
let mut edges = Vec::with_capacity(16);
|
let mut edges = Vec::with_capacity(16);
|
||||||
let mut ctx = PathfinderCtx {
|
let mut ctx = PathfinderCtx {
|
||||||
edges: &mut edges,
|
edges: &mut edges,
|
||||||
|
|
|
@ -7,9 +7,9 @@ use azalea_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
|
use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
|
||||||
use crate::pathfinder::{astar, costs::*};
|
use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
|
||||||
|
|
||||||
pub fn basic_move(ctx: &mut PathfinderCtx, node: BlockPos) {
|
pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
|
||||||
forward_move(ctx, node);
|
forward_move(ctx, node);
|
||||||
ascend_move(ctx, node);
|
ascend_move(ctx, node);
|
||||||
descend_move(ctx, node);
|
descend_move(ctx, node);
|
||||||
|
@ -18,9 +18,9 @@ pub fn basic_move(ctx: &mut PathfinderCtx, node: BlockPos) {
|
||||||
downward_move(ctx, node);
|
downward_move(ctx, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forward_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let offset = BlockPos::new(dir.x(), 0, dir.z());
|
let offset = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
|
|
||||||
let mut cost = SPRINT_ONE_BLOCK_COST;
|
let mut cost = SPRINT_ONE_BLOCK_COST;
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ fn execute_forward_move(mut ctx: ExecuteCtx) {
|
||||||
ctx.sprint(SprintDirection::Forward);
|
ctx.sprint(SprintDirection::Forward);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ascend_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let offset = BlockPos::new(dir.x(), 1, dir.z());
|
let offset = RelBlockPos::new(dir.x(), 1, dir.z());
|
||||||
|
|
||||||
let break_cost_1 = ctx
|
let break_cost_1 = ctx
|
||||||
.world
|
.world
|
||||||
|
@ -126,7 +126,7 @@ fn execute_ascend_move(mut ctx: ExecuteCtx) {
|
||||||
+ x_axis as f64 * (target_center.z - position.z).abs();
|
+ x_axis as f64 * (target_center.z - position.z).abs();
|
||||||
|
|
||||||
let lateral_motion = x_axis as f64 * physics.velocity.z + z_axis as f64 * physics.velocity.x;
|
let lateral_motion = x_axis as f64 * physics.velocity.z + z_axis as f64 * physics.velocity.x;
|
||||||
if lateral_motion > 0.1 {
|
if lateral_motion.abs() > 0.1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +147,9 @@ pub fn ascend_is_reached(
|
||||||
BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
|
BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn descend_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let dir_delta = BlockPos::new(dir.x(), 0, dir.z());
|
let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
let new_horizontal_position = pos + dir_delta;
|
let new_horizontal_position = pos + dir_delta;
|
||||||
|
|
||||||
let break_cost_1 = ctx
|
let break_cost_1 = ctx
|
||||||
|
@ -271,9 +271,9 @@ pub fn descend_is_reached(
|
||||||
&& (position.y - target.y as f64) < 0.5
|
&& (position.y - target.y as f64) < 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let dir_delta = BlockPos::new(dir.x(), 0, dir.z());
|
let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
let gap_horizontal_position = pos + dir_delta;
|
let gap_horizontal_position = pos + dir_delta;
|
||||||
let new_horizontal_position = pos + dir_delta * 2;
|
let new_horizontal_position = pos + dir_delta * 2;
|
||||||
|
|
||||||
|
@ -323,12 +323,12 @@ fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diagonal_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let right = dir.right();
|
let right = dir.right();
|
||||||
let offset = BlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
|
let offset = RelBlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
|
||||||
let left_pos = BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
|
let left_pos = RelBlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
|
||||||
let right_pos = BlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
|
let right_pos = RelBlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
|
||||||
|
|
||||||
// +0.001 so it doesn't unnecessarily go diagonal sometimes
|
// +0.001 so it doesn't unnecessarily go diagonal sometimes
|
||||||
let mut cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
|
let mut cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
|
||||||
|
@ -369,7 +369,7 @@ fn execute_diagonal_move(mut ctx: ExecuteCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go directly down, usually by mining.
|
/// Go directly down, usually by mining.
|
||||||
fn downward_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn downward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
// make sure we land on a solid block after breaking the one below us
|
// make sure we land on a solid block after breaking the one below us
|
||||||
if !ctx.world.is_block_solid(pos.down(2)) {
|
if !ctx.world.is_block_solid(pos.down(2)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -16,15 +16,16 @@ use parking_lot::RwLock;
|
||||||
use super::{
|
use super::{
|
||||||
astar,
|
astar,
|
||||||
mining::MiningCache,
|
mining::MiningCache,
|
||||||
|
rel_block_pos::RelBlockPos,
|
||||||
world::{is_block_state_passable, CachedWorld},
|
world::{is_block_state_passable, CachedWorld},
|
||||||
};
|
};
|
||||||
use crate::{auto_tool::best_tool_in_hotbar_for_block, JumpEvent, LookAtEvent};
|
use crate::{auto_tool::best_tool_in_hotbar_for_block, JumpEvent, LookAtEvent};
|
||||||
|
|
||||||
type Edge = astar::Edge<BlockPos, MoveData>;
|
type Edge = astar::Edge<RelBlockPos, MoveData>;
|
||||||
|
|
||||||
pub type SuccessorsFn = fn(&mut PathfinderCtx, BlockPos);
|
pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
|
||||||
|
|
||||||
pub fn default_move(ctx: &mut PathfinderCtx, node: BlockPos) {
|
pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
|
||||||
basic::basic_move(ctx, node);
|
basic::basic_move(ctx, node);
|
||||||
parkour::parkour_move(ctx, node);
|
parkour::parkour_move(ctx, node);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,18 @@ use azalea_core::{direction::CardinalDirection, position::BlockPos};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
|
use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
|
||||||
use crate::pathfinder::{astar, costs::*};
|
use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};
|
||||||
|
|
||||||
pub fn parkour_move(ctx: &mut PathfinderCtx, node: BlockPos) {
|
pub fn parkour_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
|
||||||
parkour_forward_1_move(ctx, node);
|
parkour_forward_1_move(ctx, node);
|
||||||
parkour_forward_2_move(ctx, node);
|
parkour_forward_2_move(ctx, node);
|
||||||
parkour_forward_3_move(ctx, node);
|
parkour_forward_3_move(ctx, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parkour_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn parkour_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
for dir in CardinalDirection::iter() {
|
for dir in CardinalDirection::iter() {
|
||||||
let gap_offset = BlockPos::new(dir.x(), 0, dir.z());
|
let gap_offset = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
let offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
|
|
||||||
// make sure we actually have to jump
|
// make sure we actually have to jump
|
||||||
if ctx.world.is_block_solid((pos + gap_offset).down(1)) {
|
if ctx.world.is_block_solid((pos + gap_offset).down(1)) {
|
||||||
|
@ -63,11 +63,11 @@ fn parkour_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parkour_forward_2_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn parkour_forward_2_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
'dir: for dir in CardinalDirection::iter() {
|
'dir: for dir in CardinalDirection::iter() {
|
||||||
let gap_1_offset = BlockPos::new(dir.x(), 0, dir.z());
|
let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
let gap_2_offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
let offset = BlockPos::new(dir.x() * 3, 0, dir.z() * 3);
|
let offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
|
||||||
|
|
||||||
// make sure we actually have to jump
|
// make sure we actually have to jump
|
||||||
if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
|
if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
|
||||||
|
@ -117,12 +117,12 @@ fn parkour_forward_2_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parkour_forward_3_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
|
fn parkour_forward_3_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
|
||||||
'dir: for dir in CardinalDirection::iter() {
|
'dir: for dir in CardinalDirection::iter() {
|
||||||
let gap_1_offset = BlockPos::new(dir.x(), 0, dir.z());
|
let gap_1_offset = RelBlockPos::new(dir.x(), 0, dir.z());
|
||||||
let gap_2_offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
let gap_2_offset = RelBlockPos::new(dir.x() * 2, 0, dir.z() * 2);
|
||||||
let gap_3_offset = BlockPos::new(dir.x() * 3, 0, dir.z() * 3);
|
let gap_3_offset = RelBlockPos::new(dir.x() * 3, 0, dir.z() * 3);
|
||||||
let offset = BlockPos::new(dir.x() * 4, 0, dir.z() * 4);
|
let offset = RelBlockPos::new(dir.x() * 4, 0, dir.z() * 4);
|
||||||
|
|
||||||
// make sure we actually have to jump
|
// make sure we actually have to jump
|
||||||
if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
|
if ctx.world.is_block_solid((pos + gap_1_offset).down(1))
|
||||||
|
|
114
azalea/src/pathfinder/rel_block_pos.rs
Normal file
114
azalea/src/pathfinder/rel_block_pos.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
use std::ops::{Add, Mul};
|
||||||
|
|
||||||
|
use azalea_core::position::BlockPos;
|
||||||
|
|
||||||
|
/// An offset from a block position.
|
||||||
|
///
|
||||||
|
/// This fits in 64 bits, so it's more efficient than a BlockPos in some cases.
|
||||||
|
///
|
||||||
|
/// The X and Z are limited to ±32k.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
|
pub struct RelBlockPos {
|
||||||
|
pub x: i16,
|
||||||
|
/// Note that the y isn't relative.
|
||||||
|
pub y: i32,
|
||||||
|
pub z: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelBlockPos {
|
||||||
|
pub fn get_origin(origin: BlockPos) -> Self {
|
||||||
|
Self::new(0, origin.y, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn new(x: i16, y: i32, z: i16) -> Self {
|
||||||
|
Self { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn apply(self, origin: BlockPos) -> BlockPos {
|
||||||
|
BlockPos::new(origin.x + self.x as i32, self.y, origin.z + self.z as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`RelBlockPos`] from a given origin and new position.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_origin(origin: BlockPos, new: BlockPos) -> Self {
|
||||||
|
Self {
|
||||||
|
x: (new.x - origin.x) as i16,
|
||||||
|
y: new.y,
|
||||||
|
z: (new.z - origin.z) as i16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn up(&self, y: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y + y,
|
||||||
|
z: self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn down(&self, y: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y - y,
|
||||||
|
z: self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn north(&self, z: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y,
|
||||||
|
z: self.z - z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn south(&self, z: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y,
|
||||||
|
z: self.z + z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn east(&self, x: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x + x,
|
||||||
|
y: self.y,
|
||||||
|
z: self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn west(&self, x: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x - x,
|
||||||
|
y: self.y,
|
||||||
|
z: self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<RelBlockPos> for RelBlockPos {
|
||||||
|
type Output = RelBlockPos;
|
||||||
|
|
||||||
|
fn add(self, rhs: RelBlockPos) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x + rhs.x,
|
||||||
|
y: self.y + rhs.y,
|
||||||
|
z: self.z + rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Mul<i16> for RelBlockPos {
|
||||||
|
type Output = RelBlockPos;
|
||||||
|
|
||||||
|
fn mul(self, rhs: i16) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x * rhs,
|
||||||
|
y: self.y * rhs as i32,
|
||||||
|
z: self.z * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,12 +11,16 @@ use azalea_core::{
|
||||||
use azalea_physics::collision::BlockWithShape;
|
use azalea_physics::collision::BlockWithShape;
|
||||||
use azalea_world::Instance;
|
use azalea_world::Instance;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use super::mining::MiningCache;
|
use super::{mining::MiningCache, rel_block_pos::RelBlockPos};
|
||||||
|
|
||||||
/// An efficient representation of the world used for the pathfinder.
|
/// An efficient representation of the world used for the pathfinder.
|
||||||
pub struct CachedWorld {
|
pub struct CachedWorld {
|
||||||
|
/// The origin that the [`RelBlockPos`] types will be relative to. This is
|
||||||
|
/// for an optimization that reduces the size of the block positions
|
||||||
|
/// that are used by the pathfinder.
|
||||||
|
origin: BlockPos,
|
||||||
|
|
||||||
min_y: i32,
|
min_y: i32,
|
||||||
world_lock: Arc<RwLock<Instance>>,
|
world_lock: Arc<RwLock<Instance>>,
|
||||||
|
|
||||||
|
@ -27,7 +31,7 @@ pub struct CachedWorld {
|
||||||
|
|
||||||
cached_blocks: UnsafeCell<CachedSections>,
|
cached_blocks: UnsafeCell<CachedSections>,
|
||||||
|
|
||||||
cached_mining_costs: UnsafeCell<FxHashMap<BlockPos, f32>>,
|
cached_mining_costs: UnsafeCell<Box<[(RelBlockPos, f32)]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -82,15 +86,20 @@ pub struct CachedSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedWorld {
|
impl CachedWorld {
|
||||||
pub fn new(world_lock: Arc<RwLock<Instance>>) -> Self {
|
pub fn new(world_lock: Arc<RwLock<Instance>>, origin: BlockPos) -> Self {
|
||||||
let min_y = world_lock.read().chunks.min_y;
|
let min_y = world_lock.read().chunks.min_y;
|
||||||
Self {
|
Self {
|
||||||
|
origin,
|
||||||
min_y,
|
min_y,
|
||||||
world_lock,
|
world_lock,
|
||||||
cached_chunks: Default::default(),
|
cached_chunks: Default::default(),
|
||||||
last_chunk_cache_index: Default::default(),
|
last_chunk_cache_index: Default::default(),
|
||||||
cached_blocks: Default::default(),
|
cached_blocks: Default::default(),
|
||||||
cached_mining_costs: Default::default(),
|
// this uses about 12mb of memory. it *really* helps though.
|
||||||
|
cached_mining_costs: UnsafeCell::new(
|
||||||
|
vec![(RelBlockPos::new(i16::MAX, i32::MAX, i16::MAX), 0.); 2usize.pow(20)]
|
||||||
|
.into_boxed_slice(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +211,11 @@ impl CachedWorld {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_block_passable(&self, pos: BlockPos) -> bool {
|
pub fn is_block_passable(&self, pos: RelBlockPos) -> bool {
|
||||||
|
self.is_block_pos_passable(pos.apply(self.origin))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_block_pos_passable(&self, pos: BlockPos) -> bool {
|
||||||
let (section_pos, section_block_pos) =
|
let (section_pos, section_block_pos) =
|
||||||
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
||||||
let index = u16::from(section_block_pos) as usize;
|
let index = u16::from(section_block_pos) as usize;
|
||||||
|
@ -220,7 +233,11 @@ impl CachedWorld {
|
||||||
passable
|
passable
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_block_solid(&self, pos: BlockPos) -> bool {
|
pub fn is_block_solid(&self, pos: RelBlockPos) -> bool {
|
||||||
|
self.is_block_pos_solid(pos.apply(self.origin))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_block_pos_solid(&self, pos: BlockPos) -> bool {
|
||||||
let (section_pos, section_block_pos) =
|
let (section_pos, section_block_pos) =
|
||||||
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
||||||
let index = u16::from(section_block_pos) as usize;
|
let index = u16::from(section_block_pos) as usize;
|
||||||
|
@ -240,25 +257,40 @@ impl CachedWorld {
|
||||||
|
|
||||||
/// Returns how much it costs to break this block. Returns 0 if the block is
|
/// Returns how much it costs to break this block. Returns 0 if the block is
|
||||||
/// already passable.
|
/// already passable.
|
||||||
pub fn cost_for_breaking_block(&self, pos: BlockPos, mining_cache: &MiningCache) -> f32 {
|
pub fn cost_for_breaking_block(&self, pos: RelBlockPos, mining_cache: &MiningCache) -> f32 {
|
||||||
// SAFETY: pathfinding is single-threaded
|
// SAFETY: pathfinding is single-threaded
|
||||||
let cached_mining_costs = unsafe { &mut *self.cached_mining_costs.get() };
|
let cached_mining_costs = unsafe { &mut *self.cached_mining_costs.get() };
|
||||||
|
// 20 bits total:
|
||||||
if let Some(&cost) = cached_mining_costs.get(&pos) {
|
// 8 bits for x, 4 bits for y, 8 bits for z
|
||||||
return cost;
|
let hash_index =
|
||||||
|
(pos.x as usize & 0xff) << 12 | (pos.y as usize & 0xf) << 8 | (pos.z as usize & 0xff);
|
||||||
|
debug_assert!(hash_index < 1048576);
|
||||||
|
let &(cached_pos, potential_cost) =
|
||||||
|
unsafe { cached_mining_costs.get_unchecked(hash_index) };
|
||||||
|
if cached_pos == pos {
|
||||||
|
return potential_cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cost = self.uncached_cost_for_breaking_block(pos, mining_cache);
|
let cost = self.uncached_cost_for_breaking_block(pos, mining_cache);
|
||||||
cached_mining_costs.insert(pos, cost);
|
unsafe {
|
||||||
|
*cached_mining_costs.get_unchecked_mut(hash_index) = (pos, cost);
|
||||||
|
};
|
||||||
|
|
||||||
cost
|
cost
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uncached_cost_for_breaking_block(&self, pos: BlockPos, mining_cache: &MiningCache) -> f32 {
|
fn uncached_cost_for_breaking_block(
|
||||||
|
&self,
|
||||||
|
pos: RelBlockPos,
|
||||||
|
mining_cache: &MiningCache,
|
||||||
|
) -> f32 {
|
||||||
if self.is_block_passable(pos) {
|
if self.is_block_passable(pos) {
|
||||||
// if the block is passable then it doesn't need to be broken
|
// if the block is passable then it doesn't need to be broken
|
||||||
return 0.;
|
return 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pos = pos.apply(self.origin);
|
||||||
|
|
||||||
let (section_pos, section_block_pos) =
|
let (section_pos, section_block_pos) =
|
||||||
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
(ChunkSectionPos::from(pos), ChunkSectionBlockPos::from(pos));
|
||||||
|
|
||||||
|
@ -341,7 +373,7 @@ impl CachedWorld {
|
||||||
mining_cost
|
mining_cost
|
||||||
}) else {
|
}) else {
|
||||||
// the chunk isn't loaded
|
// the chunk isn't loaded
|
||||||
let cost = if self.is_block_solid(pos) {
|
let cost = if self.is_block_pos_solid(pos) {
|
||||||
// assume it's unbreakable if it's solid and out of render distance
|
// assume it's unbreakable if it's solid and out of render distance
|
||||||
f32::INFINITY
|
f32::INFINITY
|
||||||
} else {
|
} else {
|
||||||
|
@ -400,22 +432,28 @@ impl CachedWorld {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this block and the block above are passable
|
/// Whether this block and the block above are passable
|
||||||
pub fn is_passable(&self, pos: BlockPos) -> bool {
|
pub fn is_passable(&self, pos: RelBlockPos) -> bool {
|
||||||
self.is_block_passable(pos) && self.is_block_passable(pos.up(1))
|
self.is_passable_at_block_pos(pos.apply(self.origin))
|
||||||
|
}
|
||||||
|
fn is_passable_at_block_pos(&self, pos: BlockPos) -> bool {
|
||||||
|
self.is_block_pos_passable(pos) && self.is_block_pos_passable(pos.up(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cost_for_passing(&self, pos: BlockPos, mining_cache: &MiningCache) -> f32 {
|
pub fn cost_for_passing(&self, pos: RelBlockPos, mining_cache: &MiningCache) -> f32 {
|
||||||
self.cost_for_breaking_block(pos, mining_cache)
|
self.cost_for_breaking_block(pos, mining_cache)
|
||||||
+ self.cost_for_breaking_block(pos.up(1), mining_cache)
|
+ self.cost_for_breaking_block(pos.up(1), mining_cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether we can stand in this position. Checks if the block below is
|
/// Whether we can stand in this position. Checks if the block below is
|
||||||
/// solid, and that the two blocks above that are passable.
|
/// solid, and that the two blocks above that are passable.
|
||||||
pub fn is_standable(&self, pos: BlockPos) -> bool {
|
pub fn is_standable(&self, pos: RelBlockPos) -> bool {
|
||||||
self.is_block_solid(pos.down(1)) && self.is_passable(pos)
|
self.is_standable_at_block_pos(pos.apply(self.origin))
|
||||||
|
}
|
||||||
|
fn is_standable_at_block_pos(&self, pos: BlockPos) -> bool {
|
||||||
|
self.is_block_pos_solid(pos.down(1)) && self.is_passable_at_block_pos(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cost_for_standing(&self, pos: BlockPos, mining_cache: &MiningCache) -> f32 {
|
pub fn cost_for_standing(&self, pos: RelBlockPos, mining_cache: &MiningCache) -> f32 {
|
||||||
if !self.is_block_solid(pos.down(1)) {
|
if !self.is_block_solid(pos.down(1)) {
|
||||||
return f32::INFINITY;
|
return f32::INFINITY;
|
||||||
}
|
}
|
||||||
|
@ -423,7 +461,7 @@ impl CachedWorld {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the amount of air blocks until the next solid block below this one.
|
/// Get the amount of air blocks until the next solid block below this one.
|
||||||
pub fn fall_distance(&self, pos: BlockPos) -> u32 {
|
pub fn fall_distance(&self, pos: RelBlockPos) -> u32 {
|
||||||
let mut distance = 0;
|
let mut distance = 0;
|
||||||
let mut current_pos = pos.down(1);
|
let mut current_pos = pos.down(1);
|
||||||
while self.is_block_passable(current_pos) {
|
while self.is_block_passable(current_pos) {
|
||||||
|
@ -512,9 +550,9 @@ mod tests {
|
||||||
.chunks
|
.chunks
|
||||||
.set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world);
|
.set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world);
|
||||||
|
|
||||||
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())));
|
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())), BlockPos::default());
|
||||||
assert!(!ctx.is_block_passable(BlockPos::new(0, 0, 0)));
|
assert!(!ctx.is_block_pos_passable(BlockPos::new(0, 0, 0)));
|
||||||
assert!(ctx.is_block_passable(BlockPos::new(0, 1, 0),));
|
assert!(ctx.is_block_pos_passable(BlockPos::new(0, 1, 0),));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -533,9 +571,9 @@ mod tests {
|
||||||
.chunks
|
.chunks
|
||||||
.set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world);
|
.set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world);
|
||||||
|
|
||||||
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())));
|
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())), BlockPos::default());
|
||||||
assert!(ctx.is_block_solid(BlockPos::new(0, 0, 0)));
|
assert!(ctx.is_block_pos_solid(BlockPos::new(0, 0, 0)));
|
||||||
assert!(!ctx.is_block_solid(BlockPos::new(0, 1, 0)));
|
assert!(!ctx.is_block_pos_solid(BlockPos::new(0, 1, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -560,9 +598,9 @@ mod tests {
|
||||||
.chunks
|
.chunks
|
||||||
.set_block_state(&BlockPos::new(0, 3, 0), BlockState::AIR, &world);
|
.set_block_state(&BlockPos::new(0, 3, 0), BlockState::AIR, &world);
|
||||||
|
|
||||||
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())));
|
let ctx = CachedWorld::new(Arc::new(RwLock::new(world.into())), BlockPos::default());
|
||||||
assert!(ctx.is_standable(BlockPos::new(0, 1, 0)));
|
assert!(ctx.is_standable_at_block_pos(BlockPos::new(0, 1, 0)));
|
||||||
assert!(!ctx.is_standable(BlockPos::new(0, 0, 0)));
|
assert!(!ctx.is_standable_at_block_pos(BlockPos::new(0, 0, 0)));
|
||||||
assert!(!ctx.is_standable(BlockPos::new(0, 2, 0)));
|
assert!(!ctx.is_standable_at_block_pos(BlockPos::new(0, 2, 0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue