aboutsummaryrefslogtreecommitdiff
path: root/azalea/src/bot.rs
blob: 13b33bb045547d40294d2af394b8c2a3e1cc3c09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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::<LookAtEvent>()
            .add_event::<JumpEvent>()
            .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<Entity, (Without<Bot>, With<Local>, With<Player>)>,
) {
    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<JumpEvent>) {
    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<LookAtEvent>,
    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::<Self>()
            .add(BotPlugin)
            .add(PathfinderPlugin)
            .add(ContainerPlugin)
            .add(AutoRespawnPlugin)
    }
}