//! Commands for debugging and getting the current state of the bot. use std::{any::Any, env, fs::File, io::Write, thread, time::Duration}; use azalea::{ BlockPos, brigadier::prelude::*, chunks::ReceiveChunkEvent, inventory, packet::game, pathfinder::{ ExecutingPath, Pathfinder, custom_state::CustomPathfinderStateRef, mining::MiningCache, moves::MovesCtx, positions::RelBlockPos, world::CachedWorld, }, }; use azalea_core::hit_result::HitResult; use azalea_entity::metadata; use azalea_inventory::{Menu, components::MaxStackSize}; use azalea_world::{Worlds, chunk::storage::WeakChunkStorage}; use bevy_app::AppExit; use bevy_ecs::{message::Messages, query::With, world::EntityRef}; use super::Ctx; use crate::commands::Dispatcher; pub fn register(commands: &mut Dispatcher) { commands.register(literal("ping").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.reply("pong!"); Ok(1) })); commands.register( literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| { let source = ctx.source.lock(); let message = get_string(ctx, "message").unwrap(); source.bot.chat(message); Ok(1) })), ); commands.register(literal("disconnect").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.disconnect(); Ok(1) })); commands.register(literal("whereami").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); return Ok(0); }; let position = entity.position()?; source.reply(format!( "You are at {}, {}, {}", position.x, position.y, position.z )); Ok(1) })); commands.register(literal("entityid").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); return Ok(0); }; let entity_id = entity.minecraft_id()?; source.reply(format!( "Your Minecraft ID is {} and your ECS ID is {entity:?}", *entity_id )); Ok(1) })); let whereareyou = |ctx: &Ctx| { let source = ctx.source.lock(); let position = source.bot.position()?; source.reply(format!( "I'm at {}, {}, {}", position.x, position.y, position.z )); Ok(1) }; commands.register(literal("whereareyou").executes(whereareyou)); commands.register(literal("pos").executes(whereareyou)); commands.register(literal("whoareyou").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.reply(format!( "I am {} ({}, {})", source.bot.username(), source.bot.uuid(), source.bot.entity )); Ok(1) })); commands.register(literal("getdirection").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let direction = source.bot.direction()?; source.reply(format!( "I'm looking at {}, {}", direction.y_rot(), direction.x_rot() )); Ok(1) })); commands.register(literal("health").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let health = source.bot.health()?; source.reply(format!("I have {health} health")); Ok(1) })); commands.register(literal("lookingat").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let hit_result = source.bot.hit_result()?; match &hit_result { HitResult::Block(r) => { if r.miss { source.reply("I'm not looking at anything"); return Ok(0); } let block_pos = r.block_pos; let block = source.bot.world()?.read().get_block_state(block_pos); source.reply(format!("I'm looking at {block:?} at {block_pos:?}")); } HitResult::Entity(r) => { let entity_kind = source.bot.entity_ref_for(r.entity).kind()?; source.reply(format!( "I'm looking at {entity_kind} ({:?}) at {}", r.entity, r.location )); } } Ok(1) })); commands.register(literal("getblock").then(argument("x", integer()).then( argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| { let source = ctx.source.lock(); let x = get_integer(ctx, "x").unwrap(); let y = get_integer(ctx, "y").unwrap(); let z = get_integer(ctx, "z").unwrap(); println!("getblock xyz {x} {y} {z}"); let block_pos = BlockPos::new(x, y, z); let block = source.bot.world()?.read().get_block_state(block_pos); source.reply(format!("BlockKind at {block_pos} is {block:?}")); Ok(1) })), ))); commands.register(literal("getfluid").then(argument("x", integer()).then( argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| { let source = ctx.source.lock(); let x = get_integer(ctx, "x").unwrap(); let y = get_integer(ctx, "y").unwrap(); let z = get_integer(ctx, "z").unwrap(); println!("getfluid xyz {x} {y} {z}"); let block_pos = BlockPos::new(x, y, z); let block = source.bot.world()?.read().get_fluid_state(block_pos); source.reply(format!("Fluid at {block_pos} is {block:?}")); Ok(1) })), ))); commands.register(literal("inventory").executes(|ctx: &Ctx| { let source = ctx.source.lock(); for item in source.bot.menu()?.slots() { if item.is_empty() { continue; } println!("{item:?}"); for (kind, data) in item.component_patch().iter() { if let Some(data) = data { println!("- {kind} {data:?}"); } } } Ok(1) })); commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let pathfinder = source.bot.component::(); let Ok(pathfinder) = pathfinder else { source.reply("I don't have the Pathfinder component"); return Ok(1); }; source.reply(format!( "pathfinder.is_calculating: {}", pathfinder.is_calculating )); let executing_path = source.bot.component::(); let Ok(executing_path) = executing_path else { source.reply("I'm not executing a path"); return Ok(1); }; source.reply(format!( "is_path_partial: {}, path.len: {}, queued_path.len: {}", executing_path.is_path_partial, executing_path.path.len(), if let Some(queued) = &executing_path.queued_path { queued.len().to_string() } else { "n/a".to_owned() }, )); Ok(1) })); commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); return Ok(0); }; let position = entity.position()?; let position = BlockPos::from(position); let mut edges = Vec::new(); let cached_world = CachedWorld::new(source.bot.world()?, position); let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default()))); let custom_state = CustomPathfinderStateRef::default(); azalea::pathfinder::moves::default_move( &mut MovesCtx { edges: &mut edges, world: &cached_world, mining_cache: &mining_cache, custom_state: &custom_state, }, RelBlockPos::from_origin(position, position), ); if edges.is_empty() { source.reply("No possible moves."); } else { source.reply("Moves:"); for (i, edge) in edges.iter().enumerate() { source.reply(format!("{}) {edge:?}", i + 1)); } } Ok(1) })); commands.register(literal("startuseitem").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.start_use_item(); source.reply("Ok!"); Ok(1) })); commands.register(literal("maxstacksize").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let max_stack_size = source .bot .get_held_item()? .get_component::() .map_or(-1, |s| s.count); source.reply(format!("{max_stack_size}")); Ok(1) })); commands.register(literal("dimensions").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let bot_dimensions = source.bot.dimensions(); source.reply(format!("{bot_dimensions:?}")); Ok(1) })); commands.register(literal("players").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let player_entities = source .bot .nearest_entities_by::<(), With>(|_: ()| true)?; let tab_list = source.bot.tab_list()?; for player_entity in player_entities { let uuid = player_entity.uuid()?; source.reply(format!( "{} - {} ({:?})", player_entity.id(), tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()), uuid )); } Ok(1) })); commands.register(literal("enchants").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.with_registry_holder(|r| { let enchants = &r.enchantment; println!("enchants: {enchants:?}"); })?; Ok(1) })); commands.register(literal("attributes").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let attributes = source.bot.attributes(); println!("attributes: {attributes:?}"); Ok(1) })); commands.register(literal("debugecsleak").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.reply("Ok!"); source.bot.disconnect(); let ecs = source.bot.ecs.clone(); thread::spawn(move || { thread::sleep(Duration::from_secs(1)); // dump the ecs let mut ecs = ecs.write(); let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt"); let mut report = File::create(&report_path).unwrap(); let mut query = ecs.query::(); for entity in query.iter(& ecs) { writeln!(report, "Entity: {}", entity.id()).unwrap(); let archetype = entity.archetype(); let component_count = archetype.component_count(); let component_names = archetype .components() .iter() .map(|c| ecs.components().get_info(*c).unwrap().name().to_string()) .collect::>(); writeln!( report, "- {component_count} components: {}", component_names.join(", ") ) .unwrap(); } writeln!(report).unwrap(); for (info, _) in ecs.iter_resources() { let name = info.name().to_string(); writeln!(report, "Resource: {name}").unwrap(); // writeln!(report, "- Size: {} bytes", // info.layout().size()).unwrap(); match name.as_ref() { "azalea_world::container::Worlds" => { let worlds = ecs.resource::(); for (world_name, world) in &worlds.map { writeln!(report, "- Name: {world_name}").unwrap(); writeln!(report, "- Reference count: {}", world.strong_count()) .unwrap(); if let Some(world) = world.upgrade() { let world = world.read(); let chunks = &world.chunks; let chunks = (chunks as &dyn Any).downcast_ref::(); if let Some(chunks) = chunks { let strong_chunks = chunks .map .iter() .filter(|(_, v)| v.strong_count() > 0) .count(); writeln!( report, "- Chunks: {} strongly referenced, {} in map", strong_chunks, chunks.map.len() ) .unwrap(); } writeln!( report, "- Entities: {}", world.entities_by_chunk.len() ) .unwrap(); } } } "bevy_ecs::message::Messages" => { let events = ecs.resource::>(); writeln!(report, "- Event count: {}", events.len()).unwrap(); } "bevy_ecs::message::Messages" => { let events = ecs.resource::>(); writeln!(report, "- Event count: {}", events.len()).unwrap(); } _ => {} } } println!("\x1b[1mWrote report to {}\x1b[m", report_path.display()); }); Ok(1) })); commands.register(literal("exit").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.reply("bye!"); source.bot.disconnect(); let source = ctx.source.clone(); thread::spawn(move || { thread::sleep(Duration::from_secs(1)); source .lock() .bot .ecs .write() .write_message(AppExit::Success); }); Ok(1) })); }