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    /// Returns whether this block could be mined.
115    pub fn should_mine(&mut self, block: BlockPos) -> bool {
116        let block_state = self
117            .instance
118            .read()
119            .get_block_state(&block)
120            .unwrap_or_default();
121        if is_block_state_passable(block_state) {
122            // block is already passable, no need to mine it
123            return false;
124        }
125
126        true
127    }
128
129    /// Mine the block at the given position. Returns whether the block is being
130    /// mined.
131    pub fn mine(&mut self, block: BlockPos) -> bool {
132        let block_state = self
133            .instance
134            .read()
135            .get_block_state(&block)
136            .unwrap_or_default();
137        if is_block_state_passable(block_state) {
138            // block is already passable, no need to mine it
139            return false;
140        }
141
142        let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
143
144        self.set_selected_hotbar_slot_events
145            .write(SetSelectedHotbarSlotEvent {
146                entity: self.entity,
147                slot: best_tool_result.index as u8,
148            });
149
150        self.is_currently_mining = true;
151
152        self.walk(WalkDirection::None);
153        self.look_at_exact(block.center());
154        self.start_mining_events.write(StartMiningBlockEvent {
155            entity: self.entity,
156            position: block,
157        });
158
159        true
160    }
161
162    /// Mine the given block, but make sure the player is standing at the start
163    /// of the current node first.
164    pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
165        let horizontal_distance_from_start = (self.start.center() - self.position)
166            .horizontal_distance_squared()
167            .sqrt();
168        let at_start_position =
169            BlockPos::from(self.position) == self.start && horizontal_distance_from_start < 0.25;
170
171        if self.should_mine(block) {
172            if at_start_position {
173                self.look_at(block.center());
174                self.mine(block);
175            } else {
176                self.look_at(self.start.center());
177                self.walk(WalkDirection::Forward);
178            }
179            true
180        } else {
181            false
182        }
183    }
184
185    pub fn get_block_state(&self, block: BlockPos) -> BlockState {
186        self.instance
187            .read()
188            .get_block_state(&block)
189            .unwrap_or_default()
190    }
191}
192
193pub struct IsReachedCtx<'a> {
194    /// The node that we're trying to reach.
195    pub target: BlockPos,
196    /// The last node that we reached.
197    pub start: BlockPos,
198    pub position: Vec3,
199    pub physics: &'a azalea_entity::Physics,
200}
201
202/// Returns whether the entity is at the node and should start going to the
203/// next node.
204#[must_use]
205pub fn default_is_reached(
206    IsReachedCtx {
207        position,
208        target,
209        physics,
210        ..
211    }: IsReachedCtx,
212) -> bool {
213    if BlockPos::from(position) == target {
214        return true;
215    }
216
217    // this is to make it handle things like slabs correctly, if we're on the block
218    // below the target but on_ground
219    BlockPos::from(position).up(1) == target && physics.on_ground()
220}
221
222pub struct PathfinderCtx<'a> {
223    pub edges: &'a mut Vec<Edge>,
224    pub world: &'a CachedWorld,
225    pub mining_cache: &'a MiningCache,
226
227    pub custom_state: &'a CustomPathfinderStateRef,
228}