From 4242cfe56e660e689cc9c3d8371fe76bc58b41ae Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 17 Dec 2023 21:45:00 -0600 Subject: [PATCH] start implementing MineForever process --- azalea-block/src/range.rs | 16 +++ azalea-entity/src/lib.rs | 73 +++++++---- azalea/src/bot.rs | 2 +- azalea/src/lib.rs | 1 - azalea/src/pathfinder/goals.rs | 113 +----------------- azalea/src/pathfinder_extras/goals.rs | 113 ++++++++++++++++++ azalea/src/pathfinder_extras/mod.rs | 9 ++ .../pathfinder_extras/process/mine_area.rs | 44 ++----- .../pathfinder_extras/process/mine_forever.rs | 105 ++++++++++++++++ azalea/src/pathfinder_extras/process/mod.rs | 17 +++ azalea/src/{ => pathfinder_extras}/utils.rs | 90 ++++++-------- 11 files changed, 361 insertions(+), 222 deletions(-) create mode 100644 azalea/src/pathfinder_extras/goals.rs create mode 100644 azalea/src/pathfinder_extras/process/mine_forever.rs rename azalea/src/{ => pathfinder_extras}/utils.rs (53%) diff --git a/azalea-block/src/range.rs b/azalea-block/src/range.rs index 9b520d496..8c0cec607 100644 --- a/azalea-block/src/range.rs +++ b/azalea-block/src/range.rs @@ -44,3 +44,19 @@ impl Add for BlockStates { } } } + +impl From> for BlockStates { + fn from(set: HashSet) -> Self { + Self { + set: set.into_iter().map(|block| block.into()).collect(), + } + } +} + +impl From<&HashSet> for BlockStates { + fn from(set: &HashSet) -> Self { + Self { + set: set.iter().map(|&block| block.into()).collect(), + } + } +} diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 3f156c9f8..2434557d1 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -23,7 +23,7 @@ use bevy_ecs::{bundle::Bundle, component::Component}; pub use data::*; use derive_more::{Deref, DerefMut}; pub use dimensions::EntityDimensions; -use std::fmt::Debug; +use std::{f64::consts::PI, fmt::Debug}; use uuid::Uuid; pub use crate::plugin::*; @@ -390,24 +390,53 @@ impl FluidOnEyes { #[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] pub struct OnClimbable(bool); -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::PartialWorld; - -// #[test] -// fn from_mut_entity_to_ref_entity() { -// let mut world = PartialWorld::default(); -// let uuid = Uuid::from_u128(100); -// world.add_entity( -// 0, -// EntityData::new( -// uuid, -// Vec3::default(), -// EntityMetadata::Player(metadata::Player::default()), -// ), -// ); -// let entity: Entity = world.entity_mut(0).unwrap(); -// assert_eq!(entity.uuid, uuid); -// } -// } +/// Return the look direction that would make a client at `current` be +/// looking at `target`. +pub fn direction_looking_at(current: &Vec3, target: &Vec3) -> LookDirection { + // borrowed from mineflayer's Bot.lookAt because i didn't want to do math + let delta = target - current; + let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI); + let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z); + let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI); + + // clamp + let y_rot = y_rot.rem_euclid(360.0); + let x_rot = x_rot.clamp(-90.0, 90.0) % 360.0; + + LookDirection { + x_rot: x_rot as f32, + y_rot: y_rot as f32, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_direction_looking_at() { + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 0.0, 1.0)); + assert_eq!(direction.y_rot, 0.0); + assert_eq!(direction.x_rot, 0.0); + + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(1.0, 0.0, 0.0)); + assert_eq!(direction.y_rot, 270.0); + assert_eq!(direction.x_rot, 0.0); + + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 0.0, -1.0)); + assert_eq!(direction.y_rot, 180.0); + assert_eq!(direction.x_rot, 0.0); + + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(-1.0, 0.0, 0.0)); + assert_eq!(direction.y_rot, 90.0); + assert_eq!(direction.x_rot, 0.0); + + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 1.0, 0.0)); + assert_eq!(direction.y_rot, 0.0); + assert_eq!(direction.x_rot, -90.0); + + let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, -1.0, 0.0)); + assert_eq!(direction.y_rot, 0.0); + assert_eq!(direction.x_rot, 90.0); + } +} diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 89bb19215..473906bb3 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -11,12 +11,12 @@ use crate::ecs::{ system::{Commands, Query}, }; use crate::pathfinder_extras::PathfinderExtrasPlugin; -use crate::utils::direction_looking_at; use azalea_client::interact::SwingArmEvent; use azalea_client::mining::Mining; use azalea_client::TickBroadcast; use azalea_core::position::{BlockPos, Vec3}; use azalea_core::tick::GameTick; +use azalea_entity::direction_looking_at; use azalea_entity::{ clamp_look_direction, metadata::Player, EyeHeight, Jumping, LocalEntity, LookDirection, Position, diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index a755f170e..9975da288 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -15,7 +15,6 @@ pub mod pathfinder; pub mod pathfinder_extras; pub mod prelude; pub mod swarm; -pub mod utils; use app::Plugins; pub use azalea_auth as auth; diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs index 87ed3a54c..7cd491f7f 100644 --- a/azalea/src/pathfinder/goals.rs +++ b/azalea/src/pathfinder/goals.rs @@ -3,14 +3,8 @@ use std::f32::consts::SQRT_2; use azalea_core::position::{BlockPos, Vec3}; -use azalea_world::ChunkStorage; -use crate::utils::get_hit_result_while_looking_at; - -use super::{ - block_box::BlockBox, - costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST}, -}; +use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST}; pub trait Goal: Send + Sync { #[must_use] @@ -35,7 +29,7 @@ impl Goal for BlockPosGoal { } } -fn xz_heuristic(dx: f32, dz: f32) -> f32 { +pub fn xz_heuristic(dx: f32, dz: f32) -> f32 { let x = dx.abs(); let z = dz.abs(); @@ -70,7 +64,7 @@ impl Goal for XZGoal { } } -fn y_heuristic(dy: f32) -> f32 { +pub fn y_heuristic(dy: f32) -> f32 { if dy > 0.0 { *JUMP_ONE_BLOCK_COST * dy } else { @@ -183,104 +177,3 @@ impl Goal for AndGoals { self.0.iter().all(|goal| goal.success(n)) } } - -/// Move to a position where we can reach the given block. -#[derive(Debug)] -pub struct ReachBlockPosGoal { - pub pos: BlockPos, - pub chunk_storage: ChunkStorage, -} -impl Goal for ReachBlockPosGoal { - fn heuristic(&self, n: BlockPos) -> f32 { - BlockPosGoal(self.pos).heuristic(n) - } - fn success(&self, n: BlockPos) -> bool { - // only do the expensive check if we're close enough - let max_pick_range = 6; - - let distance = (self.pos - n).length_squared(); - if distance > max_pick_range * max_pick_range { - return false; - } - - let block_hit_result = get_hit_result_while_looking_at(&self.chunk_storage, n, self.pos); - - block_hit_result == self.pos - } -} - -/// Move to a position inside of the given box (inclusive, so the corners are -/// included in the box). -#[derive(Debug)] -pub struct BoxGoal(pub BlockBox); - -impl Goal for BoxGoal { - fn heuristic(&self, n: BlockPos) -> f32 { - let dx = if n.x < self.0.min().x { - self.0.min().x - n.x - } else if n.x > self.0.max().x { - n.x - self.0.max().x - } else { - 0 - }; - let dy = if n.y < self.0.min().y { - self.0.min().y - n.y - } else if n.y > self.0.max().y { - n.y - self.0.max().y - } else { - 0 - }; - let dz = if n.z < self.0.min().z { - self.0.min().z - n.z - } else if n.z > self.0.max().z { - n.z - self.0.max().z - } else { - 0 - }; - - xz_heuristic(dx as f32, dz as f32) + y_heuristic(dy as f32) - } - - fn success(&self, n: BlockPos) -> bool { - n.x >= self.0.min().x - && n.x <= self.0.max().x - && n.y >= self.0.min().y - && n.y <= self.0.max().y - && n.z >= self.0.min().z - && n.z <= self.0.max().z - } -} - -/// Move to a position where we can reach at least one block from the given box. -/// This is usually used when digging out an area. -#[derive(Debug)] -pub struct ReachBoxGoal { - pub bb: BlockBox, - pub chunk_storage: ChunkStorage, -} -impl Goal for ReachBoxGoal { - fn heuristic(&self, n: BlockPos) -> f32 { - BoxGoal(self.bb.clone()).heuristic(n) - } - - fn success(&self, n: BlockPos) -> bool { - // succeed if we're already in the box - if self.bb.contains(n) { - return true; - } - - // only do the expensive check if we're close enough - let max_pick_range = 6; - - let distance = self.bb.distance_squared_to(n); - if distance > max_pick_range * max_pick_range { - return false; - } - - // look at the closest block - let look_target = self.bb.closest_block_pos(n); - let hit_result = get_hit_result_while_looking_at(&self.chunk_storage, n, look_target); - - self.bb.contains(hit_result) - } -} diff --git a/azalea/src/pathfinder_extras/goals.rs b/azalea/src/pathfinder_extras/goals.rs new file mode 100644 index 000000000..def3b83cb --- /dev/null +++ b/azalea/src/pathfinder_extras/goals.rs @@ -0,0 +1,113 @@ +//! Slightly more unusual goals than the normal +//! [pathfinder ones](crate::pathfinder::goals). + +use azalea_core::position::BlockPos; +use azalea_world::ChunkStorage; + +use crate::pathfinder::{ + block_box::BlockBox, + goals::{xz_heuristic, y_heuristic, BlockPosGoal, Goal}, +}; + +use super::utils::get_hit_result_while_looking_at; + +/// Move to a position where we can reach the given block. +#[derive(Debug)] +pub struct ReachBlockPosGoal { + pub pos: BlockPos, + pub chunk_storage: ChunkStorage, +} +impl Goal for ReachBlockPosGoal { + fn heuristic(&self, n: BlockPos) -> f32 { + BlockPosGoal(self.pos).heuristic(n) + } + fn success(&self, n: BlockPos) -> bool { + // only do the expensive check if we're close enough + let max_pick_range = 6; + + let distance = (self.pos - n).length_squared(); + if distance > max_pick_range * max_pick_range { + return false; + } + + let block_hit_result = get_hit_result_while_looking_at(&self.chunk_storage, n, self.pos); + + block_hit_result == self.pos + } +} + +/// Move to a position inside of the given box (inclusive, so the corners are +/// included in the box). +#[derive(Debug)] +pub struct BoxGoal(pub BlockBox); + +impl Goal for BoxGoal { + fn heuristic(&self, n: BlockPos) -> f32 { + let dx = if n.x < self.0.min().x { + self.0.min().x - n.x + } else if n.x > self.0.max().x { + n.x - self.0.max().x + } else { + 0 + }; + let dy = if n.y < self.0.min().y { + self.0.min().y - n.y + } else if n.y > self.0.max().y { + n.y - self.0.max().y + } else { + 0 + }; + let dz = if n.z < self.0.min().z { + self.0.min().z - n.z + } else if n.z > self.0.max().z { + n.z - self.0.max().z + } else { + 0 + }; + + xz_heuristic(dx as f32, dz as f32) + y_heuristic(dy as f32) + } + + fn success(&self, n: BlockPos) -> bool { + n.x >= self.0.min().x + && n.x <= self.0.max().x + && n.y >= self.0.min().y + && n.y <= self.0.max().y + && n.z >= self.0.min().z + && n.z <= self.0.max().z + } +} + +/// Move to a position where we can reach at least one block from the given box. +/// This is usually used when digging out an area. +#[derive(Debug)] +pub struct ReachBoxGoal { + pub bb: BlockBox, + pub chunk_storage: ChunkStorage, +} +impl Goal for ReachBoxGoal { + fn heuristic(&self, n: BlockPos) -> f32 { + BoxGoal(self.bb.clone()).heuristic(n) + } + + fn success(&self, n: BlockPos) -> bool { + // succeed if we're already in the box + if self.bb.contains(n) { + return true; + } + + // only do the expensive check if we're close enough + let max_pick_range = 6; + + let distance = self.bb.distance_squared_to(n); + if distance > max_pick_range * max_pick_range { + return false; + } + + // look at the closest block + let look_target = self.bb.closest_block_pos(n); + let hit_result = get_hit_result_while_looking_at(&self.chunk_storage, n, look_target); + + self.bb.contains(hit_result) + } +} diff --git a/azalea/src/pathfinder_extras/mod.rs b/azalea/src/pathfinder_extras/mod.rs index 168eee37e..e8b53894a 100644 --- a/azalea/src/pathfinder_extras/mod.rs +++ b/azalea/src/pathfinder_extras/mod.rs @@ -1,8 +1,11 @@ //! Adds utility functions that all depend on the pathfinder. +pub mod goals; pub mod process; +pub mod utils; use crate::ecs::prelude::*; +use azalea_block::BlockStates; use azalea_client::Client; use azalea_core::{position::BlockPos, tick::GameTick}; use azalea_physics::PhysicsSet; @@ -30,6 +33,7 @@ impl Plugin for PathfinderExtrasPlugin { pub trait PathfinderExtrasClientExt { fn set_active_pathfinder_process(&self, process: impl Into); fn mine_area(&self, corner1: BlockPos, corner2: BlockPos); + fn mine_forever(&self, block_states: impl Into); } impl PathfinderExtrasClientExt for Client { @@ -44,4 +48,9 @@ impl PathfinderExtrasClientExt for Client { fn mine_area(&self, corner1: BlockPos, corner2: BlockPos) { self.set_active_pathfinder_process(MineArea { corner1, corner2 }); } + + fn mine_forever(&self, block_states: impl Into) { + let block_states = block_states.into(); + self.set_active_pathfinder_process(process::mine_forever::MineForever { block_states }); + } } diff --git a/azalea/src/pathfinder_extras/process/mine_area.rs b/azalea/src/pathfinder_extras/process/mine_area.rs index a8c1f7bcc..7761c6ce2 100644 --- a/azalea/src/pathfinder_extras/process/mine_area.rs +++ b/azalea/src/pathfinder_extras/process/mine_area.rs @@ -8,13 +8,11 @@ use tracing::info; use crate::{ auto_tool::StartMiningBlockWithAutoToolEvent, ecs::prelude::*, - pathfinder::{ - self, - block_box::BlockBox, - goals::{Goal, ReachBlockPosGoal}, - GotoEvent, + pathfinder::{self, block_box::BlockBox, goals::Goal, GotoEvent}, + pathfinder_extras::{ + goals::{ReachBlockPosGoal, ReachBoxGoal}, + utils::{get_reachable_blocks_around_player, pick_closest_block}, }, - utils::get_reachable_blocks_around_player, LookAtEvent, }; @@ -26,7 +24,7 @@ pub struct MineArea { pub corner2: BlockPos, } -pub fn mine_area<'a>( +pub fn mine_area( mine_area: &MineArea, commands: &mut Commands, ProcessSystemComponents { @@ -36,7 +34,7 @@ pub fn mine_area<'a>( pathfinder, mining, executing_path, - }: ProcessSystemComponents<'a>, + }: ProcessSystemComponents<'_>, goto_events: &mut EventWriter, look_at_events: &mut EventWriter, start_mining_block_events: &mut EventWriter, @@ -94,33 +92,9 @@ pub fn mine_area<'a>( if !mineable_blocks.is_empty() { // pick the closest one and mine it - let mut closest_block_pos = None; - let mut closest_distance = i32::MAX; - for block_pos in &mineable_blocks[1..] { - if block_pos.y < player_position.y { - // skip blocks below us at first - continue; - } - let distance = block_pos.distance_squared_to(&player_position); - if distance < closest_distance { - closest_block_pos = Some(*block_pos); - closest_distance = distance; - } - } - - if closest_block_pos.is_none() { - // ok now check every block if the only ones around us are below - for block_pos in &mineable_blocks { - let distance = block_pos.distance_squared_to(&player_position); - if distance < closest_distance { - closest_block_pos = Some(*block_pos); - closest_distance = distance; - } - } - } - - let closest_block_pos = closest_block_pos + let closest_block_pos = pick_closest_block(player_position, &mineable_blocks) .expect("there must be a closest block because mineable_blocks wasn't empty"); + look_at_events.send(LookAtEvent { entity, position: closest_block_pos.center(), @@ -185,7 +159,7 @@ pub fn mine_area<'a>( } else { println!("reaching for box because we're at {player_position}"); - let reach_box_goal = pathfinder::goals::ReachBoxGoal { + let reach_box_goal = ReachBoxGoal { bb: bb.clone(), chunk_storage: chunk_storage.clone(), }; diff --git a/azalea/src/pathfinder_extras/process/mine_forever.rs b/azalea/src/pathfinder_extras/process/mine_forever.rs new file mode 100644 index 000000000..ef6c4b818 --- /dev/null +++ b/azalea/src/pathfinder_extras/process/mine_forever.rs @@ -0,0 +1,105 @@ +use std::sync::Arc; + +use azalea_block::BlockStates; +use azalea_core::position::BlockPos; +use tracing::info; + +use crate::{ + auto_tool::StartMiningBlockWithAutoToolEvent, + ecs::prelude::*, + pathfinder::{self, GotoEvent}, + pathfinder_extras::{ + goals::ReachBlockPosGoal, + utils::{can_reach_block, pick_closest_block}, + }, + LookAtEvent, +}; + +use super::{Process, ProcessSystemComponents}; + +#[derive(Clone, Debug)] +pub struct MineForever { + pub block_states: BlockStates, +} + +pub fn mine_forever( + mine_forever: &MineForever, + commands: &mut Commands, + ProcessSystemComponents { + entity, + position, + instance_holder, + pathfinder, + mining, + executing_path, + }: ProcessSystemComponents<'_>, + goto_events: &mut EventWriter, + look_at_events: &mut EventWriter, + start_mining_block_events: &mut EventWriter, +) { + if pathfinder.goal.is_some() || executing_path.is_some() { + // already pathfinding + println!("currently pathfinding"); + return; + } + + if mining.is_some() { + // currently mining, so wait for that to finish + println!("currently mining"); + return; + } + let instance = &instance_holder.instance.read(); + + let target_blocks = instance + .find_blocks(position, &mine_forever.block_states) + .take(16) + .collect::>(); + + let chunk_storage = instance.chunks.clone(); + let player_position = BlockPos::from(position); + + let mineable_blocks = target_blocks + .iter() + .filter(|target_pos| can_reach_block(&chunk_storage, player_position, **target_pos)) + .copied() + .collect::>(); + + if !mineable_blocks.is_empty() { + // pick the closest one and mine it + let closest_block_pos = pick_closest_block(player_position, &mineable_blocks) + .expect("there must be a closest block because mineable_blocks wasn't empty"); + + look_at_events.send(LookAtEvent { + entity, + position: closest_block_pos.center(), + }); + start_mining_block_events.send(StartMiningBlockWithAutoToolEvent { + entity, + position: closest_block_pos, + }); + + println!("start mining block {closest_block_pos:?}"); + return; + } + + let mut potential_goals = Vec::new(); + for target_pos in target_blocks { + potential_goals.push(ReachBlockPosGoal { + pos: target_pos, + chunk_storage: chunk_storage.clone(), + }); + } + + if potential_goals.is_empty() { + info!("MineForever process is done, can't find any more blocks to mine"); + commands.entity(entity).remove::(); + return; + } + + goto_events.send(GotoEvent { + entity, + goal: Arc::new(pathfinder::goals::OrGoals(potential_goals)), + successors_fn: pathfinder::moves::default_move, + allow_mining: true, + }); +} diff --git a/azalea/src/pathfinder_extras/process/mod.rs b/azalea/src/pathfinder_extras/process/mod.rs index 741af0f76..ff0739ee9 100644 --- a/azalea/src/pathfinder_extras/process/mod.rs +++ b/azalea/src/pathfinder_extras/process/mod.rs @@ -1,4 +1,5 @@ pub mod mine_area; +pub mod mine_forever; use azalea_client::{mining::Mining, InstanceHolder}; use azalea_entity::Position; @@ -13,6 +14,7 @@ use crate::{ #[derive(Component, Clone, Debug)] pub enum Process { MineArea(mine_area::MineArea), + MineForever(mine_forever::MineForever), } impl From for Process { @@ -20,6 +22,11 @@ impl From for Process { Self::MineArea(mine_area) } } +impl From for Process { + fn from(mine_forever: mine_forever::MineForever) -> Self { + Self::MineForever(mine_forever) + } +} #[derive(Event)] pub struct SetActiveProcessEvent { @@ -86,6 +93,16 @@ pub fn process_tick( &mut start_mining_block_events, ); } + Process::MineForever(mine_forever) => { + mine_forever::mine_forever( + mine_forever, + &mut commands, + components, + &mut goto_events, + &mut look_at_events, + &mut start_mining_block_events, + ); + } } } } diff --git a/azalea/src/utils.rs b/azalea/src/pathfinder_extras/utils.rs similarity index 53% rename from azalea/src/utils.rs rename to azalea/src/pathfinder_extras/utils.rs index 6cca42edf..efd79c42f 100644 --- a/azalea/src/utils.rs +++ b/azalea/src/pathfinder_extras/utils.rs @@ -1,30 +1,9 @@ //! Random utility functions that are useful for bots. -use std::f64::consts::PI; - use azalea_core::position::{BlockPos, Vec3}; -use azalea_entity::LookDirection; +use azalea_entity::direction_looking_at; use azalea_world::ChunkStorage; -/// Return the look direction that would make a client at `current` be -/// looking at `target`. -pub fn direction_looking_at(current: &Vec3, target: &Vec3) -> LookDirection { - // borrowed from mineflayer's Bot.lookAt because i didn't want to do math - let delta = target - current; - let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI); - let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z); - let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI); - - // clamp - let y_rot = y_rot.rem_euclid(360.0); - let x_rot = x_rot.clamp(-90.0, 90.0) % 360.0; - - LookDirection { - x_rot: x_rot as f32, - y_rot: y_rot as f32, - } -} - /// Return the block that we'd be looking at if we were at a given position and /// looking at a given block. /// @@ -47,6 +26,15 @@ pub fn get_hit_result_while_looking_at( get_hit_result_while_looking_at_with_eye_position(chunk_storage, eye_position, look_target) } +pub fn can_reach_block( + chunk_storage: &ChunkStorage, + player_position: BlockPos, + look_target: BlockPos, +) -> bool { + let hit_result = get_hit_result_while_looking_at(chunk_storage, player_position, look_target); + hit_result == look_target +} + /// Return the block that we'd be looking at if our eyes are at a given position /// and looking at a given block. /// @@ -84,9 +72,7 @@ pub fn get_reachable_blocks_around_player( continue; } - let hit_result = - get_hit_result_while_looking_at(chunk_storage, player_position, block_pos); - if hit_result == block_pos { + if can_reach_block(chunk_storage, player_position, block_pos) { blocks.push(block_pos); } } @@ -96,34 +82,32 @@ pub fn get_reachable_blocks_around_player( blocks } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_direction_looking_at() { - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 0.0, 1.0)); - assert_eq!(direction.y_rot, 0.0); - assert_eq!(direction.x_rot, 0.0); - - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(1.0, 0.0, 0.0)); - assert_eq!(direction.y_rot, 270.0); - assert_eq!(direction.x_rot, 0.0); - - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 0.0, -1.0)); - assert_eq!(direction.y_rot, 180.0); - assert_eq!(direction.x_rot, 0.0); - - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(-1.0, 0.0, 0.0)); - assert_eq!(direction.y_rot, 90.0); - assert_eq!(direction.x_rot, 0.0); - - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, 1.0, 0.0)); - assert_eq!(direction.y_rot, 0.0); - assert_eq!(direction.x_rot, -90.0); +pub fn pick_closest_block(position: BlockPos, blocks: &[BlockPos]) -> Option { + // pick the closest one and mine it + let mut closest_block_pos = None; + let mut closest_distance = i32::MAX; + for block_pos in &blocks[1..] { + if block_pos.y < position.y { + // skip blocks below us at first + continue; + } + let distance = block_pos.distance_squared_to(&position); + if distance < closest_distance { + closest_block_pos = Some(*block_pos); + closest_distance = distance; + } + } - let direction = direction_looking_at(&Vec3::new(0.0, 0.0, 0.0), &Vec3::new(0.0, -1.0, 0.0)); - assert_eq!(direction.y_rot, 0.0); - assert_eq!(direction.x_rot, 90.0); + if closest_block_pos.is_none() { + // ok now check every block if the only ones around us are below + for block_pos in blocks { + let distance = block_pos.distance_squared_to(&position); + if distance < closest_distance { + closest_block_pos = Some(*block_pos); + closest_distance = distance; + } + } } + + closest_block_pos }