azalea/pathfinder/debug.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
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<PathfinderDebugParticles>>,
// chat_events is Option because the tests don't have SendChatEvent
// and we have to use ResMut<Events> because bevy doesn't support Option<EventWriter>
chat_events: Option<ResMut<Events<SendChatEvent>>>,
mut tick_count: Local<usize>,
) {
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() {
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{{color:[{r},{g},{b}],scale:{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;
}
}
}