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