azalea/pathfinder/execute/
patching.rs1use std::{cmp, collections::VecDeque, ops::RangeInclusive, sync::Arc};
2
3use azalea_core::position::BlockPos;
4use azalea_entity::inventory::Inventory;
5use azalea_world::{WorldName, Worlds};
6use bevy_ecs::{
7 entity::Entity,
8 system::{Query, Res},
9};
10use parking_lot::RwLock;
11use tracing::{debug, error, warn};
12
13use crate::pathfinder::{
14 CalculatePathCtx, ExecutingPath, Pathfinder, PathfinderOpts,
15 astar::{self, PathfinderTimeout},
16 calculate_path, call_successors_fn,
17 custom_state::CustomPathfinderState,
18 goals::BlockPosGoal,
19 mining::MiningCache,
20 moves,
21 positions::RelBlockPos,
22 world::CachedWorld,
23};
24
25#[allow(clippy::type_complexity)]
26pub fn check_for_path_obstruction(
27 mut query: Query<(
28 Entity,
29 &mut Pathfinder,
30 &mut ExecutingPath,
31 &WorldName,
32 &Inventory,
33 Option<&CustomPathfinderState>,
34 )>,
35 worlds: Res<Worlds>,
36) {
37 for (entity, mut pathfinder, mut executing_path, world_name, inventory, custom_state) in
38 &mut query
39 {
40 let Some(opts) = pathfinder.opts.clone() else {
41 continue;
42 };
43
44 let world_lock = worlds
45 .get(world_name)
46 .expect("Entity tried to pathfind but the entity isn't in a valid world");
47
48 let origin = executing_path.last_reached_node;
50 let cached_world = CachedWorld::new(world_lock, origin);
51 let mining_cache = MiningCache::new(if opts.allow_mining {
52 Some(inventory.inventory_menu.clone())
53 } else {
54 None
55 });
56 let custom_state = custom_state.cloned().unwrap_or_default();
57 let custom_state_ref = custom_state.0.read();
58 let successors = |pos: RelBlockPos| {
59 call_successors_fn(
60 &cached_world,
61 &mining_cache,
62 &custom_state_ref,
63 opts.successors_fn,
64 pos,
65 )
66 };
67
68 let Some(obstructed_index) = check_path_obstructed(
69 origin,
70 RelBlockPos::from_origin(origin, executing_path.last_reached_node),
71 &executing_path.path,
72 successors,
73 ) else {
74 continue;
75 };
76
77 drop(custom_state_ref);
78
79 warn!(
80 "path obstructed at index {obstructed_index} (starting at {:?})",
81 executing_path.last_reached_node,
82 );
83 debug!("obstructed path: {:?}", executing_path.path);
84 if obstructed_index + 5 > executing_path.path.len() {
87 debug!(
88 "obstruction is near the end of the path, truncating and marking path as partial"
89 );
90 executing_path.path.truncate(obstructed_index);
91 executing_path.is_path_partial = true;
92 continue;
93 }
94
95 let Some(opts) = pathfinder.opts.clone() else {
96 error!("got PatchExecutingPathEvent but the bot has no pathfinder opts");
97 continue;
98 };
99
100 let world_lock = worlds
101 .get(world_name)
102 .expect("Entity tried to pathfind but the entity isn't in a valid world");
103
104 let patch_end_index = cmp::min(obstructed_index + 20, executing_path.path.len() - 1);
106
107 patch_path(
108 obstructed_index..=patch_end_index,
109 &mut executing_path,
110 &mut pathfinder,
111 inventory,
112 entity,
113 world_lock,
114 custom_state.clone(),
115 opts,
116 );
117 }
118}
119
120#[allow(clippy::too_many_arguments)]
126pub fn patch_path(
127 patch_nodes: RangeInclusive<usize>,
128 executing_path: &mut ExecutingPath,
129 pathfinder: &mut Pathfinder,
130 inventory: &Inventory,
131 entity: Entity,
132 world_lock: Arc<RwLock<azalea_world::World>>,
133 custom_state: CustomPathfinderState,
134 opts: PathfinderOpts,
135) {
136 let patch_start = if *patch_nodes.start() == 0 {
137 executing_path.last_reached_node
138 } else {
139 executing_path.path[*patch_nodes.start() - 1]
140 .movement
141 .target
142 };
143
144 let patch_end = executing_path.path[*patch_nodes.end()].movement.target;
145
146 let goal = Arc::new(BlockPosGoal(patch_end));
149
150 let goto_id_atomic = pathfinder.goto_id.clone();
151 let allow_mining = opts.allow_mining;
152
153 let mining_cache = MiningCache::new(if allow_mining {
154 Some(inventory.inventory_menu.clone())
155 } else {
156 None
157 });
158
159 let path_found_event = calculate_path(CalculatePathCtx {
161 entity,
162 start: patch_start,
163 goal,
164 world_lock,
165 goto_id_atomic,
166 mining_cache,
167 custom_state,
168 opts: PathfinderOpts {
169 min_timeout: PathfinderTimeout::Nodes(10_000),
170 max_timeout: PathfinderTimeout::Nodes(10_000),
171 ..opts
172 },
173 });
174
175 pathfinder.is_calculating = false;
177
178 debug!("obstruction patch: {path_found_event:?}");
179
180 let mut new_path = VecDeque::new();
181 if *patch_nodes.start() > 0 {
182 new_path.extend(
183 executing_path
184 .path
185 .iter()
186 .take(*patch_nodes.start())
187 .cloned(),
188 );
189 }
190
191 let mut is_patch_complete = false;
192 if let Some(path_found_event) = path_found_event {
193 if let Some(found_path_patch) = path_found_event.path
194 && !found_path_patch.is_empty()
195 {
196 new_path.extend(found_path_patch);
197
198 if !path_found_event.is_partial {
199 new_path.extend(executing_path.path.iter().skip(*patch_nodes.end()).cloned());
200 is_patch_complete = true;
201 debug!("the patch is not partial :)");
202 } else {
203 debug!("the patch is partial, throwing away rest of path :(");
204 }
205 }
206 } else {
207 }
209
210 executing_path.path = new_path;
211 if !is_patch_complete {
212 executing_path.is_path_partial = true;
213 }
214}
215
216pub fn check_path_obstructed<SuccessorsFn>(
221 origin: BlockPos,
222 mut current_position: RelBlockPos,
223 path: &VecDeque<astar::Edge<BlockPos, moves::MoveData>>,
224 successors_fn: SuccessorsFn,
225) -> Option<usize>
226where
227 SuccessorsFn: Fn(RelBlockPos) -> Vec<astar::Edge<RelBlockPos, moves::MoveData>>,
228{
229 for (i, edge) in path.iter().enumerate() {
230 let movement_target = RelBlockPos::from_origin(origin, edge.movement.target);
231
232 let mut found_edge = None;
233 for candidate_edge in successors_fn(current_position) {
234 if candidate_edge.movement.target == movement_target {
235 found_edge = Some(candidate_edge);
236 break;
237 }
238 }
239
240 current_position = movement_target;
241 if found_edge
243 .map(|found_edge| found_edge.cost > edge.cost)
244 .unwrap_or(true)
245 {
246 if i == 0 {
249 warn!("path obstructed at index 0 ({edge:?}), ignoring");
250 continue;
251 }
252
253 return Some(i);
254 }
255 }
256
257 None
258}