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
112
113
114
115
116
|
use azalea_client::{chat::SendChatEvent, local_player::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. You may want to run `/gamerule
/// sendCommandFeedback false` to hide the "Displaying particle minecraft:dust"
/// spam.
///
/// ```
/// # use azalea::prelude::*;
/// # use azalea::pathfinder::debug::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, edge) in executing_path.path.iter().enumerate() {
let movement = &edge.movement;
let end = movement.target;
let start_vec3 = start.center();
let end_vec3 = end.center();
let step_count = (start_vec3.distance_to(end_vec3) * 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;
}
}
}
|