1use std::{env, fs::File, io::Write, thread, time::Duration};
4
5use azalea::{
6 BlockPos,
7 brigadier::prelude::*,
8 chunks::ReceiveChunkEvent,
9 packet::game,
10 pathfinder::{
11 ExecutingPath, Pathfinder, custom_state::CustomPathfinderStateRef, mining::MiningCache,
12 moves::PathfinderCtx, rel_block_pos::RelBlockPos, world::CachedWorld,
13 },
14};
15use azalea_core::hit_result::HitResult;
16use azalea_entity::{EntityKindComponent, metadata};
17use azalea_inventory::components::MaxStackSize;
18use azalea_world::InstanceContainer;
19use bevy_app::AppExit;
20use bevy_ecs::{message::Messages, query::With, world::EntityRef};
21use parking_lot::Mutex;
22
23use super::{CommandSource, Ctx};
24
25pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
26 commands.register(literal("ping").executes(|ctx: &Ctx| {
27 let source = ctx.source.lock();
28 source.reply("pong!");
29 1
30 }));
31
32 commands.register(literal("disconnect").executes(|ctx: &Ctx| {
33 let source = ctx.source.lock();
34 source.bot.disconnect();
35 1
36 }));
37
38 commands.register(literal("whereami").executes(|ctx: &Ctx| {
39 let source = ctx.source.lock();
40 let Some(entity) = source.entity() else {
41 source.reply("You aren't in render distance!");
42 return 0;
43 };
44 let position = entity.position();
45 source.reply(format!(
46 "You are at {}, {}, {}",
47 position.x, position.y, position.z
48 ));
49 1
50 }));
51
52 commands.register(literal("entityid").executes(|ctx: &Ctx| {
53 let source = ctx.source.lock();
54 let Some(entity) = source.entity() else {
55 source.reply("You aren't in render distance!");
56 return 0;
57 };
58 let entity_id = entity.minecraft_id();
59 source.reply(format!(
60 "Your Minecraft ID is {} and your ECS ID is {entity:?}",
61 *entity_id
62 ));
63 1
64 }));
65
66 let whereareyou = |ctx: &Ctx| {
67 let source = ctx.source.lock();
68 let position = source.bot.position();
69 source.reply(format!(
70 "I'm at {}, {}, {}",
71 position.x, position.y, position.z
72 ));
73 1
74 };
75 commands.register(literal("whereareyou").executes(whereareyou));
76 commands.register(literal("pos").executes(whereareyou));
77
78 commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
79 let source = ctx.source.lock();
80 source.reply(format!(
81 "I am {} ({}, {})",
82 source.bot.username(),
83 source.bot.uuid(),
84 source.bot.entity
85 ));
86 1
87 }));
88
89 commands.register(literal("getdirection").executes(|ctx: &Ctx| {
90 let source = ctx.source.lock();
91 let direction = source.bot.direction();
92 source.reply(format!(
93 "I'm looking at {}, {}",
94 direction.y_rot(),
95 direction.x_rot()
96 ));
97 1
98 }));
99
100 commands.register(literal("health").executes(|ctx: &Ctx| {
101 let source = ctx.source.lock();
102
103 let health = source.bot.health();
104 source.reply(format!("I have {health} health"));
105 1
106 }));
107
108 commands.register(literal("lookingat").executes(|ctx: &Ctx| {
109 let source = ctx.source.lock();
110
111 let hit_result = source.bot.hit_result();
112
113 match &hit_result {
114 HitResult::Block(r) => {
115 if r.miss {
116 source.reply("I'm not looking at anything");
117 return 0;
118 }
119 let block_pos = r.block_pos;
120 let block = source.bot.world().read().get_block_state(block_pos);
121 source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
122 }
123 HitResult::Entity(r) => {
124 let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
125 source.reply(format!(
126 "I'm looking at {entity_kind} ({:?}) at {}",
127 r.entity, r.location
128 ));
129 }
130 }
131
132 1
133 }));
134
135 commands.register(literal("getblock").then(argument("x", integer()).then(
136 argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
137 let source = ctx.source.lock();
138 let x = get_integer(ctx, "x").unwrap();
139 let y = get_integer(ctx, "y").unwrap();
140 let z = get_integer(ctx, "z").unwrap();
141 println!("getblock xyz {x} {y} {z}");
142 let block_pos = BlockPos::new(x, y, z);
143 let block = source.bot.world().read().get_block_state(block_pos);
144 source.reply(format!("BlockKind at {block_pos} is {block:?}"));
145 1
146 })),
147 )));
148 commands.register(literal("getfluid").then(argument("x", integer()).then(
149 argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
150 let source = ctx.source.lock();
151 let x = get_integer(ctx, "x").unwrap();
152 let y = get_integer(ctx, "y").unwrap();
153 let z = get_integer(ctx, "z").unwrap();
154 println!("getfluid xyz {x} {y} {z}");
155 let block_pos = BlockPos::new(x, y, z);
156 let block = source.bot.world().read().get_fluid_state(block_pos);
157 source.reply(format!("Fluid at {block_pos} is {block:?}"));
158 1
159 })),
160 )));
161
162 commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
163 let source = ctx.source.lock();
164 let pathfinder = source.bot.get_component::<Pathfinder>();
165 let Some(pathfinder) = pathfinder else {
166 source.reply("I don't have the Pathfinder component");
167 return 1;
168 };
169 source.reply(format!(
170 "pathfinder.is_calculating: {}",
171 pathfinder.is_calculating
172 ));
173
174 let executing_path = source.bot.get_component::<ExecutingPath>();
175 let Some(executing_path) = executing_path else {
176 source.reply("I'm not executing a path");
177 return 1;
178 };
179 source.reply(format!(
180 "is_path_partial: {}, path.len: {}, queued_path.len: {}",
181 executing_path.is_path_partial,
182 executing_path.path.len(),
183 if let Some(queued) = &executing_path.queued_path {
184 queued.len().to_string()
185 } else {
186 "n/a".to_owned()
187 },
188 ));
189 1
190 }));
191 commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
192 let source = ctx.source.lock();
193
194 let Some(entity) = source.entity() else {
195 source.reply("You aren't in render distance!");
196 return 0;
197 };
198 let position = entity.position();
199 let position = BlockPos::from(position);
200
201 let mut edges = Vec::new();
202 let cached_world = CachedWorld::new(source.bot.world(), position);
203 let mining_cache = MiningCache::new(None);
204 let custom_state = CustomPathfinderStateRef::default();
205
206 azalea::pathfinder::moves::default_move(
207 &mut PathfinderCtx {
208 edges: &mut edges,
209 world: &cached_world,
210 mining_cache: &mining_cache,
211 custom_state: &custom_state,
212 },
213 RelBlockPos::from_origin(position, position),
214 );
215
216 if edges.is_empty() {
217 source.reply("No possible moves.");
218 } else {
219 source.reply("Moves:");
220 for (i, edge) in edges.iter().enumerate() {
221 source.reply(format!("{}) {edge:?}", i + 1));
222 }
223 }
224
225 1
226 }));
227
228 commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
229 let source = ctx.source.lock();
230 source.bot.start_use_item();
231 source.reply("Ok!");
232 1
233 }));
234 commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
235 let source = ctx.source.lock();
236 let max_stack_size = source
237 .bot
238 .get_held_item()
239 .get_component::<MaxStackSize>()
240 .map_or(-1, |s| s.count);
241 source.reply(format!("{max_stack_size}"));
242 1
243 }));
244
245 commands.register(literal("dimensions").executes(|ctx: &Ctx| {
246 let source = ctx.source.lock();
247 let bot_dimensions = source.bot.dimensions();
248 source.reply(format!("{bot_dimensions:?}"));
249 1
250 }));
251
252 commands.register(literal("players").executes(|ctx: &Ctx| {
253 let source = ctx.source.lock();
254 let player_entities = source
255 .bot
256 .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
257 let tab_list = source.bot.tab_list();
258 for player_entity in player_entities {
259 let uuid = player_entity.uuid();
260 source.reply(format!(
261 "{} - {} ({:?})",
262 player_entity.id(),
263 tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
264 uuid
265 ));
266 }
267 1
268 }));
269
270 commands.register(literal("enchants").executes(|ctx: &Ctx| {
271 let source = ctx.source.lock();
272 source.bot.with_registry_holder(|r| {
273 let enchants = &r.enchantment;
274 println!("enchants: {enchants:?}");
275 });
276 1
277 }));
278
279 commands.register(literal("attributes").executes(|ctx: &Ctx| {
280 let source = ctx.source.lock();
281 let attributes = source.bot.attributes();
282 println!("attributes: {attributes:?}");
283 1
284 }));
285
286 commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
287 let source = ctx.source.lock();
288
289 source.reply("Ok!");
290
291
292
293 source.bot.disconnect();
294
295 let ecs = source.bot.ecs.clone();
296 thread::spawn(move || {
297 thread::sleep(Duration::from_secs(1));
298 let mut ecs = ecs.write();
301
302 let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
303 let mut report = File::create(&report_path).unwrap();
304
305 let mut query = ecs.query::<EntityRef>();
306 for entity in query.iter(& ecs) {
307 writeln!(report, "Entity: {}", entity.id()).unwrap();
308 let archetype = entity.archetype();
309 let component_count = archetype.component_count();
310
311 let component_names = archetype
312 .components()
313 .iter()
314 .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
315 .collect::<Vec<_>>();
316 writeln!(
317 report,
318 "- {component_count} components: {}",
319 component_names.join(", ")
320 )
321 .unwrap();
322 }
323
324 writeln!(report).unwrap();
325
326
327 for (info, _) in ecs.iter_resources() {
328 let name = info.name().to_string();
329 writeln!(report, "Resource: {name}").unwrap();
330 match name.as_ref() {
334 "azalea_world::container::InstanceContainer" => {
335 let instance_container = ecs.resource::<InstanceContainer>();
336
337 for (instance_name, instance) in &instance_container.instances {
338 writeln!(report, "- Name: {instance_name}").unwrap();
339 writeln!(report, "- Reference count: {}", instance.strong_count())
340 .unwrap();
341 if let Some(instance) = instance.upgrade() {
342 let instance = instance.read();
343 let strong_chunks = instance
344 .chunks
345 .map
346 .iter()
347 .filter(|(_, v)| v.strong_count() > 0)
348 .count();
349 writeln!(
350 report,
351 "- Chunks: {} strongly referenced, {} in map",
352 strong_chunks,
353 instance.chunks.map.len()
354 )
355 .unwrap();
356 writeln!(
357 report,
358 "- Entities: {}",
359 instance.entities_by_chunk.len()
360 )
361 .unwrap();
362 }
363 }
364 }
365 "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
366 let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
367 writeln!(report, "- Event count: {}", events.len()).unwrap();
368 }
369 "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
370 let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
371 writeln!(report, "- Event count: {}", events.len()).unwrap();
372 }
373
374 _ => {}
375 }
376 }
377
378 println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
379 });
380
381 1
382 }));
383
384 commands.register(literal("exit").executes(|ctx: &Ctx| {
385 let source = ctx.source.lock();
386 source.reply("bye!");
387
388 source.bot.disconnect();
389
390 let source = ctx.source.clone();
391 thread::spawn(move || {
392 thread::sleep(Duration::from_secs(1));
393
394 source
395 .lock()
396 .bot
397 .ecs
398 .write()
399 .write_message(AppExit::Success);
400 });
401
402 1
403 }));
404}