1use std::f64::consts::PI;
2
3use azalea_client::mining::Mining;
4use azalea_core::{
5 position::{BlockPos, Vec3},
6 tick::GameTick,
7};
8use azalea_entity::{
9 Jumping, LocalEntity, LookDirection, Position, clamp_look_direction,
10 dimensions::EntityDimensions, metadata::Player, update_dimensions,
11};
12use azalea_physics::PhysicsSystems;
13use bevy_app::Update;
14use bevy_ecs::prelude::*;
15use tracing::trace;
16
17use crate::{
18 Client,
19 app::{App, Plugin, PluginGroup, PluginGroupBuilder},
20 ecs::{
21 component::Component,
22 entity::Entity,
23 query::{With, Without},
24 system::{Commands, Query},
25 },
26};
27
28#[derive(Clone, Default)]
29pub struct BotPlugin;
30impl Plugin for BotPlugin {
31 fn build(&self, app: &mut App) {
32 app.add_message::<LookAtEvent>()
33 .add_message::<JumpEvent>()
34 .add_systems(
35 Update,
36 (
37 insert_bot,
38 look_at_listener
39 .before(clamp_look_direction)
40 .after(update_dimensions),
41 jump_listener,
42 ),
43 )
44 .add_systems(
45 GameTick,
46 stop_jumping
47 .after(PhysicsSystems)
48 .after(azalea_client::movement::send_player_input_packet),
49 );
50 }
51}
52
53#[derive(Component, Default)]
58pub struct Bot {
59 jumping_once: bool,
60}
61
62#[allow(clippy::type_complexity)]
64fn insert_bot(
65 mut commands: Commands,
66 mut query: Query<Entity, (Without<Bot>, With<LocalEntity>, With<Player>)>,
67) {
68 for entity in &mut query {
69 commands.entity(entity).insert(Bot::default());
70 }
71}
72
73fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) {
74 for (mut jumping, mut bot) in &mut query {
75 if bot.jumping_once && **jumping {
76 bot.jumping_once = false;
77 **jumping = false;
78 }
79 }
80}
81
82impl Client {
83 pub fn jump(&self) {
85 let mut ecs = self.ecs.write();
86 ecs.write_message(JumpEvent {
87 entity: self.entity,
88 });
89 }
90
91 pub fn look_at(&self, position: Vec3) {
95 let mut ecs = self.ecs.write();
96 ecs.write_message(LookAtEvent {
97 entity: self.entity,
98 position,
99 });
100 }
101
102 pub async fn wait_ticks(&self, n: usize) {
109 let mut receiver = self.get_tick_broadcaster();
110 for _ in 0..n {
111 let _ = receiver.recv().await;
112 }
113 }
114 pub async fn wait_updates(&self, n: usize) {
124 let mut receiver = self.get_update_broadcaster();
125 for _ in 0..n {
126 let _ = receiver.recv().await;
127 }
128 }
129
130 pub async fn mine(&self, position: BlockPos) {
135 self.start_mining(position);
136
137 let mut receiver = self.get_tick_broadcaster();
138 while receiver.recv().await.is_ok() {
139 let ecs = self.ecs.read();
140 if ecs.get::<Mining>(self.entity).is_none() {
141 break;
142 }
143 }
144 }
145}
146
147#[derive(Message)]
149pub struct JumpEvent {
150 pub entity: Entity,
151}
152
153pub fn jump_listener(
154 mut query: Query<(&mut Jumping, &mut Bot)>,
155 mut events: MessageReader<JumpEvent>,
156) {
157 for event in events.read() {
158 if let Ok((mut jumping, mut bot)) = query.get_mut(event.entity) {
159 **jumping = true;
160 bot.jumping_once = true;
161 }
162 }
163}
164
165#[derive(Message)]
167pub struct LookAtEvent {
168 pub entity: Entity,
169 pub position: Vec3,
171}
172fn look_at_listener(
173 mut events: MessageReader<LookAtEvent>,
174 mut query: Query<(&Position, &EntityDimensions, &mut LookDirection)>,
175) {
176 for event in events.read() {
177 if let Ok((position, dimensions, mut look_direction)) = query.get_mut(event.entity) {
178 let new_look_direction =
179 direction_looking_at(position.up(dimensions.eye_height.into()), event.position);
180
181 trace!("look at {} (currently at {})", event.position, **position);
182 look_direction.update(new_look_direction);
183 }
184 }
185}
186
187pub fn direction_looking_at(current: Vec3, target: Vec3) -> LookDirection {
190 let delta = target - current;
192 let y_rot = (PI - f64::atan2(-delta.x, -delta.z)) * (180.0 / PI);
193 let ground_distance = f64::sqrt(delta.x * delta.x + delta.z * delta.z);
194 let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI);
195
196 LookDirection::new(y_rot as f32, x_rot as f32)
197}
198
199pub struct DefaultBotPlugins;
202
203impl PluginGroup for DefaultBotPlugins {
204 fn build(self) -> PluginGroupBuilder {
205 PluginGroupBuilder::start::<Self>()
206 .add(BotPlugin)
207 .add(crate::pathfinder::PathfinderPlugin)
208 .add(crate::container::ContainerPlugin)
209 .add(crate::auto_respawn::AutoRespawnPlugin)
210 .add(crate::accept_resource_packs::AcceptResourcePacksPlugin)
211 .add(crate::tick_broadcast::TickBroadcastPlugin)
212 .add(crate::events::EventsPlugin)
213 .add(crate::auto_reconnect::AutoReconnectPlugin)
214 }
215}