azalea/pathfinder/moves/
mod.rs

1pub mod basic;
2pub mod parkour;
3
4use std::{
5    fmt::{self, Debug},
6    sync::Arc,
7};
8
9use azalea_block::BlockState;
10use azalea_client::{
11    SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
12    inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent,
13};
14use azalea_core::position::{BlockPos, Vec3};
15use azalea_inventory::Menu;
16use azalea_world::Instance;
17use bevy_ecs::{entity::Entity, event::EventWriter};
18use parking_lot::RwLock;
19
20use super::{
21    astar,
22    custom_state::CustomPathfinderStateRef,
23    mining::MiningCache,
24    rel_block_pos::RelBlockPos,
25    world::{CachedWorld, is_block_state_passable},
26};
27use crate::{JumpEvent, LookAtEvent, auto_tool::best_tool_in_hotbar_for_block};
28
29type Edge = astar::Edge<RelBlockPos, MoveData>;
30
31pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);
32
33pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
34    basic::basic_move(ctx, node);
35    parkour::parkour_move(ctx, node);
36}
37
38#[derive(Clone)]
39pub struct MoveData {
40    /// Use the context to determine what events should be sent to complete this
41    /// movement.
42    pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
43    /// Whether we've reached the target.
44    pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
45}
46impl Debug for MoveData {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.debug_struct("MoveData")
49            // .field("move_kind", &self.move_kind)
50            .finish()
51    }
52}
53
54pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
55    pub entity: Entity,
56    /// The node that we're trying to reach.
57    pub target: BlockPos,
58    /// The last node that we reached.
59    pub start: BlockPos,
60    pub position: Vec3,
61    pub physics: &'a azalea_entity::Physics,
62    pub is_currently_mining: bool,
63    pub instance: Arc<RwLock<Instance>>,
64    pub menu: Menu,
65
66    pub look_at_events: &'a mut EventWriter<'w1, LookAtEvent>,
67    pub sprint_events: &'a mut EventWriter<'w2, StartSprintEvent>,
68    pub walk_events: &'a mut EventWriter<'w3, StartWalkEvent>,
69    pub jump_events: &'a mut EventWriter<'w4, JumpEvent>,
70    pub start_mining_events: &'a mut EventWriter<'w5, StartMiningBlockEvent>,
71    pub set_selected_hotbar_slot_events: &'a mut EventWriter<'w6, SetSelectedHotbarSlotEvent>,
72}
73
74impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_> {
75    pub fn look_at(&mut self, position: Vec3) {
76        self.look_at_events.write(LookAtEvent {
77            entity: self.entity,
78            position: Vec3 {
79                x: position.x,
80                // look forward
81                y: self.position.up(1.53).y,
82                z: position.z,
83            },
84        });
85    }
86
87    pub fn look_at_exact(&mut self, position: Vec3) {
88        self.look_at_events.write(LookAtEvent {
89            entity: self.entity,
90            position,
91        });
92    }
93
94    pub fn sprint(&mut self, direction: SprintDirection) {
95        self.sprint_events.write(StartSprintEvent {
96            entity: self.entity,
97            direction,
98        });
99    }
100
101    pub fn walk(&mut self, direction: WalkDirection) {
102        self.walk_events.write(StartWalkEvent {
103            entity: self.entity,
104            direction,
105        });
106    }
107
108    pub fn jump(&mut self) {
109        self.jump_events.write(JumpEvent {
110            entity: self.entity,
111        });
112    }
113
114    pub fn jump_if_in_water(&mut self) {
115        if self.physics.is_in_water() {
116            self.jump();
117        }
118    }
119
120    /// Returns whether this block could be mined.
121    pub fn should_mine(&mut self, block: BlockPos) -> bool {
122        let block_state = self
123            .instance
124            .read()
125            .get_block_state(block)
126            .unwrap_or_default();
127        if is_block_state_passable(block_state) {
128            // block is already passable, no need to mine it
129            return false;
130        }
131
132        true
133    }
134
135    /// Mine the block at the given position. Returns whether the block is being
136    /// mined.
137    pub fn mine(&mut self, block: BlockPos) -> bool {
138        let block_state = self
139            .instance
140            .read()
141            .get_block_state(block)
142            .unwrap_or_default();
143        if is_block_state_passable(block_state) {
144            // block is already passable, no need to mine it
145            return false;
146        }
147
148        let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
149
150        self.set_selected_hotbar_slot_events
151            .write(SetSelectedHotbarSlotEvent {
152                entity: self.entity,
153                slot: best_tool_result.index as u8,
154            });
155
156        self.is_currently_mining = true;
157
158        self.walk(WalkDirection::None);
159        self.look_at_exact(block.center());
160        self.start_mining_events.write(StartMiningBlockEvent {
161            entity: self.entity,
162            position: block,
163        });
164
165        true
166    }
167
168    /// Mine the given block, but make sure the player is standing at the start
169    /// of the current node first.
170    pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
171        let horizontal_distance_from_start = (self.start.center() - self.position)
172            .horizontal_distance_squared()
173            .sqrt();
174        let at_start_position =
175            BlockPos::from(self.position) == self.start && horizontal_distance_from_start < 0.25;
176
177        if self.should_mine(block) {
178            if at_start_position {
179                self.look_at(block.center());
180                self.mine(block);
181            } else {
182                self.look_at(self.start.center());
183                self.walk(WalkDirection::Forward);
184            }
185            true
186        } else {
187            false
188        }
189    }
190
191    pub fn get_block_state(&self, block: BlockPos) -> BlockState {
192        self.instance
193            .read()
194            .get_block_state(block)
195            .unwrap_or_default()
196    }
197}
198
199pub struct IsReachedCtx<'a> {
200    /// The node that we're trying to reach.
201    pub target: BlockPos,
202    /// The last node that we reached.
203    pub start: BlockPos,
204    pub position: Vec3,
205    pub physics: &'a azalea_entity::Physics,
206}
207
208/// Returns whether the entity is at the node and should start going to the
209/// next node.
210#[must_use]
211pub fn default_is_reached(
212    IsReachedCtx {
213        position,
214        target,
215        physics,
216        ..
217    }: IsReachedCtx,
218) -> bool {
219    if BlockPos::from(position) == target {
220        return true;
221    }
222
223    // this is to make it handle things like slabs correctly, if we're on the block
224    // below the target but on_ground
225    BlockPos::from(position).up(1) == target && physics.on_ground()
226}
227
228pub struct PathfinderCtx<'a> {
229    pub edges: &'a mut Vec<Edge>,
230    pub world: &'a CachedWorld,
231    pub mining_cache: &'a MiningCache,
232
233    pub custom_state: &'a CustomPathfinderStateRef,
234}