Skip to main content

azalea/pathfinder/moves/
mod.rs

1pub mod basic;
2pub mod parkour;
3pub mod uncommon;
4
5use std::{
6    fmt::{self, Debug},
7    sync::Arc,
8};
9
10use azalea_block::BlockState;
11use azalea_client::{
12    PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
13    inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent,
14};
15use azalea_core::position::{BlockPos, Vec3};
16use azalea_inventory::Menu;
17use azalea_registry::builtin::BlockKind;
18use azalea_world::World;
19use bevy_ecs::{entity::Entity, message::MessageWriter, system::Commands, world::EntityWorldMut};
20use parking_lot::RwLock;
21use tracing::debug;
22
23use super::{
24    astar,
25    custom_state::CustomPathfinderStateRef,
26    mining::MiningCache,
27    positions::RelBlockPos,
28    world::{CachedWorld, is_block_state_passable},
29};
30use crate::{
31    auto_tool::best_tool_in_hotbar_for_block,
32    bot::{JumpEvent, LookAtEvent},
33    pathfinder::player_pos_to_block_pos,
34};
35
36type Edge = astar::Edge<RelBlockPos, MoveData>;
37
38pub type SuccessorsFn = fn(&mut MovesCtx, RelBlockPos);
39
40/// Re-implement certain bugs and quirks that Baritone has, and disable
41/// movements that Baritone doesn't have.
42///
43/// Meant to help with debugging when directly comparing against Baritone.
44pub const BARITONE_COMPAT: bool = false;
45
46pub fn default_move(ctx: &mut MovesCtx, node: RelBlockPos) {
47    basic::basic_move(ctx, node);
48    parkour::parkour_move(ctx, node);
49    uncommon::uncommon_move(ctx, node);
50}
51
52#[derive(Clone)]
53pub struct MoveData {
54    /// Use the context to determine what events should be sent to complete this
55    /// movement.
56    pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
57    /// Whether we've reached the target.
58    pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
59}
60impl Debug for MoveData {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("MoveData")
63            // .field("move_kind", &self.move_kind)
64            .finish()
65    }
66}
67
68pub struct ExecuteCtx<'s, 'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
69    pub entity: Entity,
70    /// The node that we're trying to reach.
71    pub target: BlockPos,
72    /// The last node that we reached.
73    pub start: BlockPos,
74    pub position: Vec3,
75    pub physics: &'a azalea_entity::Physics,
76    pub is_currently_mining: bool,
77    pub can_mine: bool,
78    pub world: Arc<RwLock<World>>,
79    pub menu: Menu,
80
81    pub commands: &'a mut Commands<'w1, 's>,
82    pub look_at_events: &'a mut MessageWriter<'w2, LookAtEvent>,
83    pub sprint_events: &'a mut MessageWriter<'w3, StartSprintEvent>,
84    pub walk_events: &'a mut MessageWriter<'w4, StartWalkEvent>,
85    pub jump_events: &'a mut MessageWriter<'w5, JumpEvent>,
86    pub start_mining_events: &'a mut MessageWriter<'w6, StartMiningBlockEvent>,
87}
88
89impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_, '_> {
90    pub fn on_tick_start(&mut self) {
91        self.set_sneaking(false);
92    }
93
94    pub fn look_at(&mut self, position: Vec3) {
95        self.look_at_events.write(LookAtEvent {
96            entity: self.entity,
97            position: Vec3 {
98                x: position.x,
99                // look forward
100                y: self.position.up(1.53).y,
101                z: position.z,
102            },
103        });
104    }
105
106    pub fn look_at_exact(&mut self, position: Vec3) {
107        self.look_at_events.write(LookAtEvent {
108            entity: self.entity,
109            position,
110        });
111    }
112
113    pub fn sprint(&mut self, direction: SprintDirection) {
114        self.sprint_events.write(StartSprintEvent {
115            entity: self.entity,
116            direction,
117        });
118    }
119
120    pub fn walk(&mut self, direction: WalkDirection) {
121        self.walk_events.write(StartWalkEvent {
122            entity: self.entity,
123            direction,
124        });
125    }
126
127    pub fn jump(&mut self) {
128        self.jump_events.write(JumpEvent {
129            entity: self.entity,
130        });
131    }
132
133    fn set_sneaking(&mut self, sneaking: bool) {
134        self.commands
135            .entity(self.entity)
136            .queue(move |mut entity: EntityWorldMut<'_>| {
137                if let Some(mut physics_state) = entity.get_mut::<PhysicsState>() {
138                    physics_state.trying_to_crouch = sneaking;
139                }
140            });
141    }
142    pub fn sneak(&mut self) {
143        self.set_sneaking(true);
144    }
145
146    pub fn jump_if_in_water(&mut self) {
147        if self.physics.is_in_water() {
148            self.jump();
149        }
150    }
151
152    /// Returns whether this block could be mined.
153    pub fn should_mine(&mut self, block: BlockPos) -> bool {
154        let block_state = self.world.read().get_block_state(block).unwrap_or_default();
155        should_mine_block_state(block_state)
156    }
157
158    /// Mine the block at the given position.
159    ///
160    /// Returns whether the block is being mined.
161    pub fn mine(&mut self, block: BlockPos) -> bool {
162        if !self.can_mine {
163            return false;
164        }
165
166        let block_state = self.world.read().get_block_state(block).unwrap_or_default();
167        if is_block_state_passable(block_state) {
168            // block is already passable, no need to mine it
169            return false;
170        }
171
172        let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
173        debug!("best tool for {block_state:?}: {best_tool_result:?}");
174
175        self.commands.trigger(SetSelectedHotbarSlotEvent {
176            entity: self.entity,
177            slot: best_tool_result.index as u8,
178        });
179
180        self.is_currently_mining = true;
181
182        self.walk(WalkDirection::None);
183        self.look_at_exact(block.center());
184        self.start_mining_events.write(StartMiningBlockEvent {
185            entity: self.entity,
186            position: block,
187            force: true,
188        });
189
190        true
191    }
192
193    /// Mine the given block, but make sure the player is standing at the start
194    /// of the current node first.
195    pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
196        let horizontal_distance_from_start = (self.start.center() - self.position)
197            .horizontal_distance_squared()
198            .sqrt();
199        let at_start_position = player_pos_to_block_pos(self.position) == self.start
200            && horizontal_distance_from_start < 0.25;
201
202        if self.should_mine(block) {
203            if at_start_position {
204                self.look_at(block.center());
205                self.mine(block);
206            } else {
207                self.look_at(self.start.center());
208                self.walk(WalkDirection::Forward);
209            }
210            true
211        } else {
212            false
213        }
214    }
215
216    pub fn get_block_state(&self, block: BlockPos) -> BlockState {
217        self.world.read().get_block_state(block).unwrap_or_default()
218    }
219}
220
221pub fn should_mine_block_state(block_state: BlockState) -> bool {
222    if is_block_state_passable(block_state) || BlockKind::from(block_state) == BlockKind::Water {
223        // block is already passable, no need to mine it
224        return false;
225    }
226
227    true
228}
229
230pub struct IsReachedCtx<'a> {
231    /// The node that we're trying to reach.
232    pub target: BlockPos,
233    /// The last node that we reached.
234    pub start: BlockPos,
235    pub position: Vec3,
236    pub physics: &'a azalea_entity::Physics,
237}
238
239/// Returns whether the entity is at the node and should start going to the
240/// next node.
241#[must_use]
242pub fn default_is_reached(
243    IsReachedCtx {
244        position,
245        target,
246        physics,
247        ..
248    }: IsReachedCtx,
249) -> bool {
250    let block_pos = player_pos_to_block_pos(position);
251    if block_pos == target {
252        return true;
253    }
254    // it's fine if we go over the target while swimming
255    if physics.is_in_water() && block_pos.down(1) == target {
256        return true;
257    }
258
259    false
260}
261
262pub struct MovesCtx<'a> {
263    pub edges: &'a mut Vec<Edge>,
264    pub world: &'a CachedWorld,
265    pub mining_cache: &'a MiningCache,
266    pub custom_state: &'a CustomPathfinderStateRef,
267}