diff --git a/azalea/src/pathfinder/debug.rs b/azalea/src/pathfinder/debug.rs new file mode 100644 index 000000000..201803c9b --- /dev/null +++ b/azalea/src/pathfinder/debug.rs @@ -0,0 +1,113 @@ +use azalea_client::{chat::SendChatEvent, InstanceHolder}; +use azalea_core::position::Vec3; +use bevy_ecs::prelude::*; + +use super::ExecutingPath; + +/// A component that makes bots run /particle commands while pathfinding to show +/// where they're going. This requires the bots to have server operator +/// permissions, and it'll make them spam *a lot* of commands. +/// +/// ``` +/// # use azalea::prelude::*; +/// # use azalea::pathfinder::PathfinderDebugParticles; +/// # #[derive(Component, Clone, Default)] +/// # pub struct State; +/// +/// async fn handle(mut bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> { +/// match event { +/// azalea::Event::Init => { +/// bot.ecs +/// .lock() +/// .entity_mut(bot.entity) +/// .insert(PathfinderDebugParticles); +/// } +/// _ => {} +/// } +/// Ok(()) +/// } +/// ``` +#[derive(Component)] +pub struct PathfinderDebugParticles; + +pub fn debug_render_path_with_particles( + mut query: Query<(Entity, &ExecutingPath, &InstanceHolder), With>, + // chat_events is Option because the tests don't have SendChatEvent + // and we have to use ResMut because bevy doesn't support Option + chat_events: Option>>, + mut tick_count: Local, +) { + let Some(mut chat_events) = chat_events else { + return; + }; + if *tick_count >= 2 { + *tick_count = 0; + } else { + *tick_count += 1; + return; + } + for (entity, executing_path, instance_holder) in &mut query { + if executing_path.path.is_empty() { + continue; + } + + let chunks = &instance_holder.instance.read().chunks; + + let mut start = executing_path.last_reached_node; + for (i, movement) in executing_path.path.iter().enumerate() { + // /particle dust 0 1 1 1 ~ ~ ~ 0 0 0.2 0 100 + + let end = movement.target; + + let start_vec3 = start.center(); + let end_vec3 = end.center(); + + let step_count = (start_vec3.distance_to_sqr(&end_vec3).sqrt() * 4.0) as usize; + + let target_block_state = chunks.get_block_state(&movement.target).unwrap_or_default(); + let above_target_block_state = chunks + .get_block_state(&movement.target.up(1)) + .unwrap_or_default(); + // this isn't foolproof, there might be another block that could be mined + // depending on the move, but it's good enough for debugging + // purposes + let is_mining = !super::world::is_block_state_passable(target_block_state) + || !super::world::is_block_state_passable(above_target_block_state); + + let (r, g, b): (f64, f64, f64) = if i == 0 { + (0., 1., 0.) + } else if is_mining { + (1., 0., 0.) + } else { + (0., 1., 1.) + }; + + // interpolate between the start and end positions + for i in 0..step_count { + let percent = i as f64 / step_count as f64; + let pos = Vec3 { + x: start_vec3.x + (end_vec3.x - start_vec3.x) * percent, + y: start_vec3.y + (end_vec3.y - start_vec3.y) * percent, + z: start_vec3.z + (end_vec3.z - start_vec3.z) * percent, + }; + let particle_command = format!( + "/particle dust {r} {g} {b} {size} {start_x} {start_y} {start_z} {delta_x} {delta_y} {delta_z} 0 {count}", + size = 1, + start_x = pos.x, + start_y = pos.y, + start_z = pos.z, + delta_x = 0, + delta_y = 0, + delta_z = 0, + count = 1 + ); + chat_events.send(SendChatEvent { + entity, + content: particle_command, + }); + } + + start = movement.target; + } + } +} diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 43bb86cc9..fa37157ec 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -3,6 +3,7 @@ pub mod astar; pub mod costs; +mod debug; pub mod goals; pub mod mining; pub mod moves; @@ -23,12 +24,11 @@ use crate::ecs::{ }; use crate::pathfinder::moves::PathfinderCtx; use crate::pathfinder::world::CachedWorld; -use azalea_client::chat::SendChatEvent; use azalea_client::inventory::{InventoryComponent, InventorySet, SetSelectedHotbarSlotEvent}; use azalea_client::mining::{Mining, StartMiningBlockEvent}; use azalea_client::movement::MoveEventsSet; use azalea_client::{InstanceHolder, StartSprintEvent, StartWalkEvent}; -use azalea_core::position::{BlockPos, Vec3}; +use azalea_core::position::BlockPos; use azalea_core::tick::GameTick; use azalea_entity::metadata::Player; use azalea_entity::LocalEntity; @@ -36,11 +36,9 @@ use azalea_entity::{Physics, Position}; use azalea_physics::PhysicsSet; use azalea_world::{InstanceContainer, InstanceName}; use bevy_app::{PreUpdate, Update}; -use bevy_ecs::event::Events; use bevy_ecs::prelude::Event; use bevy_ecs::query::Changed; use bevy_ecs::schedule::IntoSystemConfigs; -use bevy_ecs::system::{Local, ResMut}; use bevy_tasks::{AsyncComputeTaskPool, Task}; use futures_lite::future; use std::collections::VecDeque; @@ -49,6 +47,8 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use tracing::{debug, error, info, trace, warn}; +use self::debug::debug_render_path_with_particles; +pub use self::debug::PathfinderDebugParticles; use self::mining::MiningCache; use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}; @@ -799,96 +799,6 @@ fn stop_pathfinding_on_instance_change( } } -/// A component that makes bots run /particle commands while pathfinding to show -/// where they're going. This requires the bots to have server operator -/// permissions, and it'll make them spam *a lot* of commands. -/// -/// ``` -/// # use azalea::prelude::*; -/// # use azalea::pathfinder::PathfinderDebugParticles; -/// # #[derive(Component, Clone, Default)] -/// # pub struct State; -/// -/// async fn handle(mut bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> { -/// match event { -/// azalea::Event::Init => { -/// bot.ecs -/// .lock() -/// .entity_mut(bot.entity) -/// .insert(PathfinderDebugParticles); -/// } -/// _ => {} -/// } -/// Ok(()) -/// } -/// ``` -#[derive(Component)] -pub struct PathfinderDebugParticles; - -fn debug_render_path_with_particles( - mut query: Query<(Entity, &ExecutingPath), With>, - // chat_events is Option because the tests don't have SendChatEvent - // and we have to use ResMut because bevy doesn't support Option - chat_events: Option>>, - mut tick_count: Local, -) { - let Some(mut chat_events) = chat_events else { - return; - }; - if *tick_count >= 2 { - *tick_count = 0; - } else { - *tick_count += 1; - return; - } - for (entity, executing_path) in &mut query { - if executing_path.path.is_empty() { - continue; - } - - let mut start = executing_path.last_reached_node; - for (i, movement) in executing_path.path.iter().enumerate() { - // /particle dust 0 1 1 1 ~ ~ ~ 0 0 0.2 0 100 - - let end = movement.target; - - let start_vec3 = start.center(); - let end_vec3 = end.center(); - - let step_count = (start_vec3.distance_to_sqr(&end_vec3).sqrt() * 4.0) as usize; - - let (r, g, b): (f64, f64, f64) = if i == 0 { (0., 1., 0.) } else { (0., 1., 1.) }; - - // interpolate between the start and end positions - for i in 0..step_count { - let percent = i as f64 / step_count as f64; - let pos = Vec3 { - x: start_vec3.x + (end_vec3.x - start_vec3.x) * percent, - y: start_vec3.y + (end_vec3.y - start_vec3.y) * percent, - z: start_vec3.z + (end_vec3.z - start_vec3.z) * percent, - }; - let particle_command = format!( - "/particle dust {r} {g} {b} {size} {start_x} {start_y} {start_z} {delta_x} {delta_y} {delta_z} 0 {count}", - size = 1, - start_x = pos.x, - start_y = pos.y, - start_z = pos.z, - delta_x = 0, - delta_y = 0, - delta_z = 0, - count = 1 - ); - chat_events.send(SendChatEvent { - entity, - content: particle_command, - }); - } - - start = movement.target; - } - } -} - pub trait Goal { #[must_use] fn heuristic(&self, n: BlockPos) -> f32; diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs index 48785ed8b..54a6dc6a8 100644 --- a/azalea/src/pathfinder/moves/basic.rs +++ b/azalea/src/pathfinder/moves/basic.rs @@ -408,14 +408,12 @@ fn execute_downward_move(mut ctx: ExecuteCtx) { if horizontal_distance_from_target > 0.25 { ctx.look_at(target_center); ctx.walk(WalkDirection::Forward); + } else if ctx.mine_while_at_start(target) { + ctx.walk(WalkDirection::None); + } else if BlockPos::from(position) != target { + ctx.look_at(target_center); + ctx.walk(WalkDirection::Forward); } else { - if ctx.mine_while_at_start(target) { - ctx.walk(WalkDirection::None); - } else if BlockPos::from(position) != target { - ctx.look_at(target_center); - ctx.walk(WalkDirection::Forward); - } else { - ctx.walk(WalkDirection::None); - } + ctx.walk(WalkDirection::None); } } diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs index 58ffeebf9..bb10b1928 100644 --- a/azalea/src/pathfinder/moves/mod.rs +++ b/azalea/src/pathfinder/moves/mod.rs @@ -167,7 +167,7 @@ impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_> { self.mine(block); } else { self.look_at(self.start.center()); - self.walk(WalkDirection::None); + self.walk(WalkDirection::Forward); } true } else {