azalea/pathfinder/moves/
mod.rs

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