use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder}; use crate::auto_respawn::AutoRespawnPlugin; use crate::container::ContainerPlugin; use crate::ecs::{ component::Component, entity::Entity, event::EventReader, query::{With, Without}, schedule::IntoSystemConfig, system::{Commands, Query}, }; use azalea_core::Vec3; use azalea_physics::{force_jump_listener, PhysicsSet}; use azalea_world::entity::{clamp_look_direction, EyeHeight, LookDirection}; use azalea_world::entity::{metadata::Player, Jumping, Local, Position}; use std::f64::consts::PI; use crate::pathfinder::PathfinderPlugin; #[derive(Clone, Default)] pub struct BotPlugin; impl Plugin for BotPlugin { fn build(&self, app: &mut App) { app.add_event::() .add_event::() .add_systems(( insert_bot, look_at_listener .before(force_jump_listener) .before(clamp_look_direction), jump_listener, stop_jumping .in_schedule(CoreSchedule::FixedUpdate) .after(PhysicsSet), )); } } /// Component for all bots. #[derive(Default, Component)] pub struct Bot { jumping_once: bool, } /// Insert the [`Bot`] component for any local players that don't have it. #[allow(clippy::type_complexity)] fn insert_bot( mut commands: Commands, mut query: Query, With, With)>, ) { for entity in &mut query { commands.entity(entity).insert(Bot::default()); } } fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) { for (mut jumping, mut bot) in &mut query { if bot.jumping_once && **jumping { bot.jumping_once = false; **jumping = false; } } } pub trait BotClientExt { fn jump(&mut self); fn look_at(&mut self, pos: Vec3); } impl BotClientExt for azalea_client::Client { /// Queue a jump for the next tick. fn jump(&mut self) { let mut ecs = self.ecs.lock(); ecs.send_event(JumpEvent(self.entity)); } /// Turn the bot's head to look at the coordinate in the world. fn look_at(&mut self, position: Vec3) { let mut ecs = self.ecs.lock(); ecs.send_event(LookAtEvent { entity: self.entity, position, }); } } /// Event to jump once. pub struct JumpEvent(pub Entity); fn jump_listener(mut query: Query<(&mut Jumping, &mut Bot)>, mut events: EventReader) { for event in events.iter() { if let Ok((mut jumping, mut bot)) = query.get_mut(event.0) { **jumping = true; bot.jumping_once = true; } } } /// Make an entity look towards a certain position in the world. pub struct LookAtEvent { pub entity: Entity, /// The position we want the entity to be looking at. pub position: Vec3, } fn look_at_listener( mut events: EventReader, mut query: Query<(&Position, &EyeHeight, &mut LookDirection)>, ) { for event in events.iter() { if let Ok((position, eye_height, mut look_direction)) = query.get_mut(event.entity) { let (y_rot, x_rot) = direction_looking_at(&position.up(eye_height.into()), &event.position); (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot); } } } /// Return the (`y_rot`, `x_rot`) that would make a client at `current` be /// looking at `target`. fn direction_looking_at(current: &Vec3, target: &Vec3) -> (f32, f32) { // borrowed from mineflayer's Bot.lookAt because i didn't want to do math let delta = target - current; let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI); let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z); let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI); (y_rot as f32, x_rot as f32) } /// A [`PluginGroup`] for the plugins that add extra bot functionality to the /// client. pub struct DefaultBotPlugins; impl PluginGroup for DefaultBotPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() .add(BotPlugin) .add(PathfinderPlugin) .add(ContainerPlugin) .add(AutoRespawnPlugin) } }