diff --git a/azalea/src/auto_tool.rs b/azalea/src/auto_tool.rs index 89b4d38d4..f35dd2447 100644 --- a/azalea/src/auto_tool.rs +++ b/azalea/src/auto_tool.rs @@ -5,7 +5,7 @@ use azalea_client::{ Client, InstanceHolder, }; use azalea_core::position::BlockPos; -use azalea_entity::{FluidOnEyes, Physics}; +use azalea_entity::{update_fluid_on_eyes, FluidOnEyes, Physics}; use azalea_inventory::{ItemSlot, Menu}; use azalea_registry::Fluid; use bevy_app::{App, Plugin, Update}; @@ -24,7 +24,9 @@ impl Plugin for AutoToolPlugin { .add_systems( Update, start_mining_block_with_auto_tool_listener - .before(azalea_client::inventory::handle_set_selected_hotbar_slot_event), + .before(azalea_client::inventory::handle_set_selected_hotbar_slot_event) + .after(update_fluid_on_eyes) + .after(azalea_client::chunks::handle_receive_chunk_events), ); } } diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 54ec1d684..48b2c8c6b 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -756,7 +756,7 @@ pub struct StopPathfindingEvent { pub force: bool, } -fn handle_stop_pathfinding_event( +pub fn handle_stop_pathfinding_event( mut events: EventReader, mut query: Query<(&mut Pathfinder, &mut ExecutingPath)>, mut walk_events: EventWriter, @@ -790,7 +790,7 @@ fn handle_stop_pathfinding_event( } } -fn stop_pathfinding_on_instance_change( +pub fn stop_pathfinding_on_instance_change( mut query: Query<(Entity, &mut ExecutingPath), Changed>, mut stop_pathfinding_events: EventWriter, ) { diff --git a/azalea/src/pathfinder_extras/mod.rs b/azalea/src/pathfinder_extras/mod.rs index 5e00ae1ea..168eee37e 100644 --- a/azalea/src/pathfinder_extras/mod.rs +++ b/azalea/src/pathfinder_extras/mod.rs @@ -2,31 +2,39 @@ pub mod process; +use crate::ecs::prelude::*; use azalea_client::Client; use azalea_core::{position::BlockPos, tick::GameTick}; +use azalea_physics::PhysicsSet; use bevy_app::Update; use crate::app::{App, Plugin}; -use self::process::{Process, SetActiveProcessEvent}; +use self::process::{mine_area::MineArea, Process, SetActiveProcessEvent}; pub struct PathfinderExtrasPlugin; impl Plugin for PathfinderExtrasPlugin { fn build(&self, app: &mut App) { app.add_event::() - .add_systems(Update, process::set_active_pathfinder_process_listener) - .add_systems(GameTick, process::process_tick); + .add_systems( + Update, + process::set_active_pathfinder_process_listener + .after(crate::pathfinder::stop_pathfinding_on_instance_change) + .before(crate::pathfinder::handle_stop_pathfinding_event), + ) + .add_systems(GameTick, process::process_tick.before(PhysicsSet)); } } pub trait PathfinderExtrasClientExt { - fn set_active_pathfinder_process(&self, process: Process); + fn set_active_pathfinder_process(&self, process: impl Into); fn mine_area(&self, corner1: BlockPos, corner2: BlockPos); } impl PathfinderExtrasClientExt for Client { - fn set_active_pathfinder_process(&self, process: Process) { + fn set_active_pathfinder_process(&self, process: impl Into) { + let process = process.into(); self.ecs.lock().send_event(SetActiveProcessEvent { entity: self.entity, process, @@ -34,6 +42,6 @@ impl PathfinderExtrasClientExt for Client { } fn mine_area(&self, corner1: BlockPos, corner2: BlockPos) { - self.set_active_pathfinder_process(Process::MineArea { corner1, corner2 }); + self.set_active_pathfinder_process(MineArea { corner1, corner2 }); } } diff --git a/azalea/src/pathfinder_extras/process/mine_area.rs b/azalea/src/pathfinder_extras/process/mine_area.rs new file mode 100644 index 000000000..a8c1f7bcc --- /dev/null +++ b/azalea/src/pathfinder_extras/process/mine_area.rs @@ -0,0 +1,232 @@ +use std::sync::Arc; + +use azalea_block::BlockState; +use azalea_core::position::BlockPos; +use azalea_world::ChunkStorage; +use tracing::info; + +use crate::{ + auto_tool::StartMiningBlockWithAutoToolEvent, + ecs::prelude::*, + pathfinder::{ + self, + block_box::BlockBox, + goals::{Goal, ReachBlockPosGoal}, + GotoEvent, + }, + utils::get_reachable_blocks_around_player, + LookAtEvent, +}; + +use super::{Process, ProcessSystemComponents}; + +#[derive(Clone, Debug)] +pub struct MineArea { + pub corner1: BlockPos, + pub corner2: BlockPos, +} + +pub fn mine_area<'a>( + mine_area: &MineArea, + commands: &mut Commands, + ProcessSystemComponents { + entity, + position, + instance_holder, + pathfinder, + mining, + executing_path, + }: ProcessSystemComponents<'a>, + 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 bb = BlockBox::new(mine_area.corner1, mine_area.corner2); + let chunk_storage = instance_holder.instance.read().chunks.clone(); + let player_position = BlockPos::from(position); + + println!("player_position: {player_position}"); + + // the index is from the top-down, so 0 means the top layer + let layer_index = determine_layer(&bb, &chunk_storage); + let layer_bb = BlockBox::new( + BlockPos::new( + bb.min().x, + i32::max(bb.min().y, bb.max().y - layer_index as i32), + bb.min().z, + ), + BlockPos::new( + bb.max().x, + i32::max(bb.min().y, bb.max().y - layer_index as i32), + bb.max().z, + ), + ); + + let reachable_blocks = get_reachable_blocks_around_player(player_position, &chunk_storage); + let mineable_blocks = reachable_blocks + .into_iter() + .filter(|block_pos| { + // must be within box + if !layer_bb.contains(*block_pos) { + return false; + } + + // and must be mineable + let block = chunk_storage.get_block_state(block_pos).unwrap_or_default(); + + is_block_mineable(block) + }) + .collect::>(); + + println!("mineable_blocks: {:?}", mineable_blocks); + + 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 + .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; + } + + // no mineable blocks, so go towards the blocks that can be mined + + let goal: Arc = if bb.distance_squared_to(player_position) < 16 * 16 { + // already close enough to the box, path to the closest + // block instead + + let mut block_positions_and_distances = Vec::new(); + for x in layer_bb.min().x..=layer_bb.max().x { + for y in layer_bb.min().y..=layer_bb.max().y { + for z in layer_bb.min().z..=layer_bb.max().z { + let block_pos = BlockPos::new(x, y, z); + + if !is_block_mineable( + chunk_storage + .get_block_state(&block_pos) + .unwrap_or_default(), + ) { + continue; + } + + let distance = block_pos.distance_squared_to(&player_position); + block_positions_and_distances.push((block_pos, distance)); + } + } + } + + if block_positions_and_distances.is_empty() { + info!("MineArea process is done, no more blocks to mine!"); + commands.entity(entity).remove::(); + return; + } + + // use the closest 64 blocks as the goals + + block_positions_and_distances.sort_by_key(|(_, distance)| *distance); + let mut goals = Vec::new(); + for (block_pos, _) in block_positions_and_distances.into_iter().take(64) { + goals.push(ReachBlockPosGoal { + pos: block_pos, + chunk_storage: chunk_storage.clone(), + }); + } + + let reach_blocks_goal = pathfinder::goals::OrGoals(goals); + + println!("reaching for block"); + + Arc::new(reach_blocks_goal) + } else { + println!("reaching for box because we're at {player_position}"); + + let reach_box_goal = pathfinder::goals::ReachBoxGoal { + bb: bb.clone(), + chunk_storage: chunk_storage.clone(), + }; + + Arc::new(reach_box_goal) + }; + + goto_events.send(GotoEvent { + entity, + goal, + successors_fn: pathfinder::moves::default_move, + allow_mining: true, + }); +} + +fn is_block_mineable(block: BlockState) -> bool { + !block.is_air() +} + +/// Determine what layer should be mined first. This is from the top-down, so 0 +/// means the top layer. +fn determine_layer(bb: &BlockBox, chunks: &ChunkStorage) -> usize { + let mut layer = 0; + let mut y = bb.max().y; + while y >= bb.min().y { + let mut x = bb.min().x; + while x <= bb.max().x { + let mut z = bb.min().z; + while z <= bb.max().z { + let block = chunks + .get_block_state(&BlockPos::new(x, y, z)) + .unwrap_or_default(); + if is_block_mineable(block) { + return layer; + } + z += 1; + } + x += 1; + } + y -= 1; + layer += 1; + } + layer +} diff --git a/azalea/src/pathfinder_extras/process/mod.rs b/azalea/src/pathfinder_extras/process/mod.rs index f40fde599..741af0f76 100644 --- a/azalea/src/pathfinder_extras/process/mod.rs +++ b/azalea/src/pathfinder_extras/process/mod.rs @@ -1,31 +1,24 @@ -use std::sync::Arc; +pub mod mine_area; -use azalea_block::BlockState; use azalea_client::{mining::Mining, InstanceHolder}; -use azalea_core::position::BlockPos; use azalea_entity::Position; -use azalea_world::ChunkStorage; -use tracing::info; use crate::{ auto_tool::StartMiningBlockWithAutoToolEvent, ecs::prelude::*, - pathfinder::{ - self, - block_box::BlockBox, - goals::{Goal, OrGoals, ReachBlockPosGoal, ReachBoxGoal}, - ExecutingPath, GotoEvent, Pathfinder, - }, - utils::get_reachable_blocks_around_player, + pathfinder::{self, ExecutingPath, GotoEvent, Pathfinder}, LookAtEvent, }; -#[derive(Component, Clone)] +#[derive(Component, Clone, Debug)] pub enum Process { - MineArea { - corner1: BlockPos, - corner2: BlockPos, - }, + MineArea(mine_area::MineArea), +} + +impl From for Process { + fn from(mine_area: mine_area::MineArea) -> Self { + Self::MineArea(mine_area) + } } #[derive(Event)] @@ -48,34 +41,13 @@ pub fn set_active_pathfinder_process_listener( } } -fn is_block_mineable(block: BlockState) -> bool { - !block.is_air() -} - -/// Determine what layer should be mined first. This is from the top-down, so 0 -/// means the top layer. -fn determine_layer(bb: &BlockBox, chunks: &ChunkStorage) -> usize { - let mut layer = 0; - let mut y = bb.max().y; - while y >= bb.min().y { - let mut x = bb.min().x; - while x <= bb.max().x { - let mut z = bb.min().z; - while z <= bb.max().z { - let block = chunks - .get_block_state(&BlockPos::new(x, y, z)) - .unwrap_or_default(); - if is_block_mineable(block) { - return layer; - } - z += 1; - } - x += 1; - } - y -= 1; - layer += 1; - } - layer +pub struct ProcessSystemComponents<'a> { + pub entity: Entity, + pub position: &'a Position, + pub instance_holder: &'a InstanceHolder, + pub pathfinder: &'a Pathfinder, + pub mining: Option<&'a Mining>, + pub executing_path: Option<&'a ExecutingPath>, } #[allow(clippy::type_complexity)] @@ -95,168 +67,24 @@ pub fn process_tick( mut start_mining_block_events: EventWriter, ) { for (entity, process, position, instance_holder, pathfinder, mining, executing_path) in &query { + let components = ProcessSystemComponents { + entity, + position, + instance_holder, + pathfinder, + mining, + executing_path, + }; match process { - Process::MineArea { corner1, corner2 } => { - if pathfinder.goal.is_some() || executing_path.is_some() { - // already pathfinding - println!("currently pathfinding"); - continue; - } - - if mining.is_some() { - // currently mining, so wait for that to finish - println!("currently mining"); - continue; - } - - let bb = BlockBox::new(*corner1, *corner2); - let chunk_storage = instance_holder.instance.read().chunks.clone(); - let player_position = BlockPos::from(position); - - println!("player_position: {player_position}"); - - // the index is from the top-down, so 0 means the top layer - let layer_index = determine_layer(&bb, &chunk_storage); - let layer_bb = BlockBox::new( - BlockPos::new( - bb.min().x, - i32::max(bb.min().y, bb.max().y - layer_index as i32), - bb.min().z, - ), - BlockPos::new( - bb.max().x, - i32::max(bb.min().y, bb.max().y - layer_index as i32), - bb.max().z, - ), + Process::MineArea(mine_area) => { + mine_area::mine_area( + mine_area, + &mut commands, + components, + &mut goto_events, + &mut look_at_events, + &mut start_mining_block_events, ); - - let reachable_blocks = - get_reachable_blocks_around_player(player_position, &chunk_storage); - let mineable_blocks = reachable_blocks - .into_iter() - .filter(|block_pos| { - // must be within box - if !layer_bb.contains(*block_pos) { - return false; - } - - // and must be mineable - let block = chunk_storage.get_block_state(block_pos).unwrap_or_default(); - - is_block_mineable(block) - }) - .collect::>(); - - println!("mineable_blocks: {:?}", mineable_blocks); - - 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.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:?}"); - continue; - } - - // no mineable blocks, so go towards the blocks that can be mined - - let goal: Arc = if bb.distance_squared_to(player_position) < 16 * 16 { - // already close enough to the box, path to the closest - // block instead - - let mut block_positions_and_distances = Vec::new(); - for x in layer_bb.min().x..=layer_bb.max().x { - for y in layer_bb.min().y..=layer_bb.max().y { - for z in layer_bb.min().z..=layer_bb.max().z { - let block_pos = BlockPos::new(x, y, z); - - if !is_block_mineable( - chunk_storage - .get_block_state(&block_pos) - .unwrap_or_default(), - ) { - continue; - } - - let distance = block_pos.distance_squared_to(&player_position); - block_positions_and_distances.push((block_pos, distance)); - } - } - } - - if block_positions_and_distances.is_empty() { - info!("MineArea process is done, no more blocks to mine!"); - commands.entity(entity).remove::(); - continue; - } - - // use the closest 64 blocks as the goals - - block_positions_and_distances.sort_by_key(|(_, distance)| *distance); - let mut goals = Vec::new(); - for (block_pos, _) in block_positions_and_distances.into_iter().take(64) { - goals.push(ReachBlockPosGoal { - pos: block_pos, - chunk_storage: chunk_storage.clone(), - }); - } - - let reach_blocks_goal = OrGoals(goals); - - println!("reaching for block"); - - Arc::new(reach_blocks_goal) - } else { - println!("reaching for box because we're at {player_position}"); - - let reach_box_goal = ReachBoxGoal { - bb: bb.clone(), - chunk_storage: chunk_storage.clone(), - }; - - Arc::new(reach_box_goal) - }; - - goto_events.send(GotoEvent { - entity, - goal, - successors_fn: pathfinder::moves::default_move, - allow_mining: true, - }); } } }