diff --git a/README.md b/README.md index b60ca34e..d7d5a2fa 100755 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A collection of Rust crates for making Minecraft bots, clients, and tools. ## Features - [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like knockback and water physics aren't yet implemented) -- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html) (parkour isn't perfect yet) +- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html) - [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html) - [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine) - [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet but it's usually fine) diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 5c1edf77..3b9d3e6f 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -4,6 +4,7 @@ #![feature(async_fn_in_trait)] #![feature(type_changing_struct_update)] #![feature(lazy_cell)] +#![feature(let_chains)] pub mod accept_resource_packs; mod auto_respawn; diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 8a84b5e9..3a75bff7 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -51,17 +51,23 @@ impl Plugin for PathfinderPlugin { fn build(&self, app: &mut App) { app.add_event::() .add_event::() + .add_event::() .add_systems( FixedUpdate, // putting systems in the FixedUpdate schedule makes them run every Minecraft tick // (every 50 milliseconds). ( - tick_execute_path - .after(PhysicsSet) - .after(azalea_client::movement::send_position), + timeout_movement, + check_node_reached, + tick_execute_path, + check_for_path_obstruction, debug_render_path_with_particles, + recalculate_near_end_of_path, + recalculate_if_has_goal_but_no_path, ) - .chain(), + .chain() + .after(PhysicsSet) + .after(azalea_client::movement::send_position), ) .add_systems(PreUpdate, add_default_pathfinder) .add_systems( @@ -70,28 +76,36 @@ impl Plugin for PathfinderPlugin { goto_listener, handle_tasks, path_found_listener, - stop_pathfinding_on_instance_change.before(walk_listener), + stop_pathfinding_on_instance_change, + handle_stop_pathfinding_event, ) - .chain(), + .chain() + .before(walk_listener), ); } } -/// A component that makes this entity able to pathfind. +/// A component that makes this client able to pathfind. #[derive(Component, Default)] pub struct Pathfinder { - pub path: VecDeque>, - pub queued_path: Option>>, - pub is_path_partial: bool, - - pub last_reached_node: Option, - pub last_node_reached_at: Option, pub goal: Option>, pub successors_fn: Option, pub is_calculating: bool, pub goto_id: Arc, } + +/// A component that's present on clients that are actively following a +/// pathfinder path. +#[derive(Component)] +pub struct ExecutingPath { + pub path: VecDeque>, + pub queued_path: Option>>, + pub last_reached_node: BlockPos, + pub last_node_reached_at: Instant, + pub is_path_partial: bool, +} + #[derive(Event)] pub struct GotoEvent { pub entity: Entity, @@ -121,6 +135,7 @@ fn add_default_pathfinder( pub trait PathfinderClientExt { fn goto(&self, goal: impl Goal + Send + Sync + 'static); + fn stop_pathfinding(&self); } impl PathfinderClientExt for azalea_client::Client { @@ -138,6 +153,13 @@ impl PathfinderClientExt for azalea_client::Client { successors_fn: moves::default_move, }); } + + fn stop_pathfinding(&self) { + self.ecs.lock().send_event(StopPathfindingEvent { + entity: self.entity, + force: false, + }); + } } #[derive(Component)] @@ -146,13 +168,18 @@ pub struct ComputePath(Task>); fn goto_listener( mut commands: Commands, mut events: EventReader, - mut query: Query<(&mut Pathfinder, &Position, &InstanceName)>, + mut query: Query<( + &mut Pathfinder, + Option<&ExecutingPath>, + &Position, + &InstanceName, + )>, instance_container: Res, ) { let thread_pool = AsyncComputeTaskPool::get(); for event in events.iter() { - let (mut pathfinder, position, instance_name) = query + let (mut pathfinder, executing_path, position, instance_name) = query .get_mut(event.entity) .expect("Called goto on an entity that's not in the world"); @@ -161,15 +188,16 @@ fn goto_listener( pathfinder.successors_fn = Some(event.successors_fn); pathfinder.is_calculating = true; - let start = if pathfinder.path.is_empty() { - BlockPos::from(position) - } else { + let start = if let Some(executing_path) = executing_path + && let Some(final_node) = executing_path.path.back() { // if we're currently pathfinding and got a goto event, start a little ahead - pathfinder + executing_path .path .get(20) - .unwrap_or_else(|| pathfinder.path.back().unwrap()) + .unwrap_or(final_node) .target + } else { + BlockPos::from(position) }; info!( "got goto, starting from {start:?} (currently at {:?})", @@ -216,7 +244,7 @@ fn goto_listener( debug!("partial: {partial:?}"); let duration = end_time - start_time; if partial { - info!("Pathfinder took {duration:?} (timed out)"); + info!("Pathfinder took {duration:?} (incomplete path)"); // wait a bit so it's not a busy loop std::thread::sleep(Duration::from_millis(100)); } else { @@ -285,46 +313,47 @@ fn handle_tasks( // set the path for the target entity when we get the PathFoundEvent fn path_found_listener( mut events: EventReader, - mut query: Query<(&mut Pathfinder, &InstanceName)>, + mut query: Query<(&mut Pathfinder, Option<&mut ExecutingPath>, &InstanceName)>, instance_container: Res, + mut commands: Commands, ) { for event in events.iter() { - let (mut pathfinder, instance_name) = query + let (mut pathfinder, executing_path, instance_name) = query .get_mut(event.entity) .expect("Path found for an entity that doesn't have a pathfinder"); if let Some(path) = &event.path { - if pathfinder.path.is_empty() { - pathfinder.path = path.to_owned(); - debug!("set path to {:?}", path.iter().take(10).collect::>()); - pathfinder.last_reached_node = Some(event.start); - pathfinder.last_node_reached_at = Some(Instant::now()); - } else { + if let Some(mut executing_path) = executing_path { let mut new_path = VecDeque::new(); // combine the old and new paths if the first node of the new path is a // successor of the last node of the old path - if let Some(first_node) = path.front() { - if let Some(last_node) = pathfinder.path.back() { - let world_lock = instance_container.get(instance_name).expect( - "Entity tried to pathfind but the entity isn't in a valid world", - ); - let successors_fn: moves::SuccessorsFn = event.successors_fn; - let ctx = PathfinderCtx::new(world_lock); - let successors = |pos: BlockPos| { - let mut edges = Vec::with_capacity(16); - successors_fn(&mut edges, &ctx, pos); - edges - }; + if let Some(last_node_of_current_path) = executing_path.path.back() { + let world_lock = instance_container + .get(instance_name) + .expect("Entity tried to pathfind but the entity isn't in a valid world"); + let successors_fn: moves::SuccessorsFn = event.successors_fn; + let ctx = PathfinderCtx::new(world_lock); + let successors = |pos: BlockPos| { + let mut edges = Vec::with_capacity(16); + successors_fn(&mut edges, &ctx, pos); + edges + }; - if successors(last_node.target) + if let Some(first_node_of_new_path) = path.front() { + if successors(last_node_of_current_path.target) .iter() - .any(|edge| edge.movement.target == first_node.target) + .any(|edge| edge.movement.target == first_node_of_new_path.target) { debug!("combining old and new paths"); - debug!("old path: {:?}", pathfinder.path.iter().collect::>()); + debug!( + "old path: {:?}", + executing_path.path.iter().collect::>() + ); debug!("new path: {:?}", path.iter().take(10).collect::>()); - new_path.extend(pathfinder.path.iter().cloned()); + new_path.extend(executing_path.path.iter().cloned()); } + } else { + new_path.extend(executing_path.path.iter().cloned()); } } @@ -334,62 +363,74 @@ fn path_found_listener( "set queued path to {:?}", new_path.iter().take(10).collect::>() ); - pathfinder.queued_path = Some(new_path); + executing_path.queued_path = Some(new_path); + executing_path.is_path_partial = event.is_partial; + } else if path.is_empty() { + debug!("calculated path is empty, so didn't add ExecutingPath"); + } else { + commands.entity(event.entity).insert(ExecutingPath { + path: path.to_owned(), + queued_path: None, + last_reached_node: event.start, + last_node_reached_at: Instant::now(), + is_path_partial: event.is_partial, + }); + debug!("set path to {:?}", path.iter().take(10).collect::>()); + debug!("partial: {}", event.is_partial); } } else { error!("No path found"); - pathfinder.path.clear(); - pathfinder.queued_path = None; + if let Some(mut executing_path) = executing_path { + // set the queued path so we don't stop in the middle of a move + executing_path.queued_path = Some(VecDeque::new()); + } else { + // wasn't executing a path, don't need to do anything + } } pathfinder.is_calculating = false; - pathfinder.is_path_partial = event.is_partial; } } -fn tick_execute_path( - mut query: Query<(Entity, &mut Pathfinder, &Position, &Physics, &InstanceName)>, - mut look_at_events: EventWriter, - mut sprint_events: EventWriter, +fn timeout_movement(mut query: Query<(&Pathfinder, &mut ExecutingPath, &Position)>) { + for (pathfinder, mut executing_path, position) in &mut query { + if executing_path.last_node_reached_at.elapsed() > Duration::from_secs(2) + && !pathfinder.is_calculating + && !executing_path.path.is_empty() + { + warn!("pathfinder timeout"); + // the path wasn't being followed anyways, so clearing it is fine + executing_path.path.clear(); + executing_path.queued_path = None; + executing_path.last_reached_node = BlockPos::from(position); + // invalidate whatever calculation we were just doing, if any + pathfinder.goto_id.fetch_add(1, atomic::Ordering::Relaxed); + // set partial to true to make sure that a recalculation will happen + executing_path.is_path_partial = true; + + // the path will get recalculated automatically because the path is + // empty + } + } +} + +fn check_node_reached( + mut query: Query<( + Entity, + &mut Pathfinder, + &mut ExecutingPath, + &Position, + &Physics, + )>, mut walk_events: EventWriter, - mut jump_events: EventWriter, - mut goto_events: EventWriter, - instance_container: Res, + mut commands: Commands, ) { - for (entity, mut pathfinder, position, physics, instance_name) in &mut query { - if pathfinder.goal.is_none() { - // no goal, no pathfinding - continue; - } - - let successors_fn: moves::SuccessorsFn = pathfinder - .successors_fn - .expect("pathfinder.successors_fn should be Some if the goal is Some"); - - let world_lock = instance_container - .get(instance_name) - .expect("Entity tried to pathfind but the entity isn't in a valid world"); - - if !pathfinder.is_calculating { - // timeout check - if let Some(last_node_reached_at) = pathfinder.last_node_reached_at { - if last_node_reached_at.elapsed() > Duration::from_secs(2) { - warn!("pathfinder timeout"); - pathfinder.path.clear(); - pathfinder.queued_path = None; - pathfinder.last_reached_node = None; - pathfinder.goto_id.fetch_add(1, atomic::Ordering::Relaxed); - // set partial to true to make sure that the recalculation happens - pathfinder.is_path_partial = true; - } - } - } - + for (entity, mut pathfinder, mut executing_path, position, physics) in &mut query { 'skip: loop { // we check if the goal was reached *before* actually executing the movement so // we don't unnecessarily execute a movement when it wasn't necessary // see if we already reached any future nodes and can skip ahead - for (i, movement) in pathfinder + for (i, movement) in executing_path .path .clone() .into_iter() @@ -399,43 +440,42 @@ fn tick_execute_path( { let is_reached_ctx = IsReachedCtx { target: movement.target, - start: pathfinder.last_reached_node.expect( - "pathfinder.last_node_reached_at should always be present if there's a path", - ), + start: executing_path.last_reached_node, position: **position, physics, }; - let extra_strict_if_last = if i == pathfinder.path.len() - 1 { + let extra_strict_if_last = if i == executing_path.path.len() - 1 { let x_difference_from_center = position.x - (movement.target.x as f64 + 0.5); let z_difference_from_center = position.z - (movement.target.z as f64 + 0.5); // this is to make sure we don't fall off immediately after finishing the path physics.on_ground - && BlockPos::from(position) == movement.target - // adding the delta like this isn't a perfect solution but it helps to make - // sure we don't keep going if our delta is high - && (x_difference_from_center + physics.delta.x).abs() < 0.2 - && (z_difference_from_center + physics.delta.z).abs() < 0.2 + && BlockPos::from(position) == movement.target + // adding the delta like this isn't a perfect solution but it helps to make + // sure we don't keep going if our delta is high + && (x_difference_from_center + physics.delta.x).abs() < 0.2 + && (z_difference_from_center + physics.delta.z).abs() < 0.2 } else { true }; if (movement.data.is_reached)(is_reached_ctx) && extra_strict_if_last { - pathfinder.path = pathfinder.path.split_off(i + 1); - pathfinder.last_reached_node = Some(movement.target); - pathfinder.last_node_reached_at = Some(Instant::now()); + executing_path.path = executing_path.path.split_off(i + 1); + executing_path.last_reached_node = movement.target; + executing_path.last_node_reached_at = Instant::now(); - if let Some(new_path) = pathfinder.queued_path.take() { + if let Some(new_path) = executing_path.queued_path.take() { debug!( "swapped path to {:?}", new_path.iter().take(10).collect::>() ); - pathfinder.path = new_path; + executing_path.path = new_path; - if pathfinder.path.is_empty() { + if executing_path.path.is_empty() { info!("the path we just swapped to was empty, so reached end of path"); walk_events.send(StartWalkEvent { entity, direction: WalkDirection::None, }); + commands.entity(entity).remove::(); break; } @@ -443,12 +483,13 @@ fn tick_execute_path( continue 'skip; } - if pathfinder.path.is_empty() { + if executing_path.path.is_empty() { debug!("pathfinder path is now empty"); walk_events.send(StartWalkEvent { entity, direction: WalkDirection::None, }); + commands.entity(entity).remove::(); if let Some(goal) = pathfinder.goal.clone() { if goal.success(movement.target) { info!("goal was reached!"); @@ -463,15 +504,119 @@ fn tick_execute_path( } break; } + } +} - if let Some(movement) = pathfinder.path.front() { +fn check_for_path_obstruction( + mut query: Query<(&Pathfinder, &mut ExecutingPath, &InstanceName)>, + instance_container: Res, +) { + for (pathfinder, mut executing_path, instance_name) in &mut query { + let Some(successors_fn) = pathfinder.successors_fn else { + continue; + }; + + let world_lock = instance_container + .get(instance_name) + .expect("Entity tried to pathfind but the entity isn't in a valid world"); + + // obstruction check (the path we're executing isn't possible anymore) + let ctx = PathfinderCtx::new(world_lock); + let successors = |pos: BlockPos| { + let mut edges = Vec::with_capacity(16); + successors_fn(&mut edges, &ctx, pos); + edges + }; + + if let Some(obstructed_index) = check_path_obstructed( + executing_path.last_reached_node, + &executing_path.path, + successors, + ) { + warn!( + "path obstructed at index {obstructed_index} (starting at {:?}, path: {:?})", + executing_path.last_reached_node, executing_path.path + ); + executing_path.path.truncate(obstructed_index); + executing_path.is_path_partial = true; + } + } +} + +fn recalculate_near_end_of_path( + mut query: Query<(Entity, &mut Pathfinder, &mut ExecutingPath)>, + mut walk_events: EventWriter, + mut goto_events: EventWriter, + mut commands: Commands, +) { + for (entity, mut pathfinder, mut executing_path) in &mut query { + let Some(successors_fn) = pathfinder.successors_fn else { + continue; + }; + + // start recalculating if the path ends soon + if (executing_path.path.len() == 20 || executing_path.path.len() < 5) + && !pathfinder.is_calculating + && executing_path.is_path_partial + { + if let Some(goal) = pathfinder.goal.as_ref().cloned() { + debug!("Recalculating path because it ends soon"); + debug!( + "recalculate_near_end_of_path executing_path.is_path_partial: {}", + executing_path.is_path_partial + ); + goto_events.send(GotoEvent { + entity, + goal, + successors_fn, + }); + pathfinder.is_calculating = true; + + if executing_path.path.is_empty() { + if let Some(new_path) = executing_path.queued_path.take() { + executing_path.path = new_path; + if executing_path.path.is_empty() { + info!("the path we just swapped to was empty, so reached end of path"); + walk_events.send(StartWalkEvent { + entity, + direction: WalkDirection::None, + }); + commands.entity(entity).remove::(); + break; + } + } else { + walk_events.send(StartWalkEvent { + entity, + direction: WalkDirection::None, + }); + commands.entity(entity).remove::(); + } + } + } else if executing_path.path.is_empty() { + // idk when this can happen but stop moving just in case + walk_events.send(StartWalkEvent { + entity, + direction: WalkDirection::None, + }); + } + } + } +} + +fn tick_execute_path( + mut query: Query<(Entity, &mut ExecutingPath, &Position, &Physics)>, + mut look_at_events: EventWriter, + mut sprint_events: EventWriter, + mut walk_events: EventWriter, + mut jump_events: EventWriter, +) { + for (entity, executing_path, position, physics) in &mut query { + if let Some(movement) = executing_path.path.front() { let ctx = ExecuteCtx { entity, target: movement.target, position: **position, - start: pathfinder.last_reached_node.expect( - "pathfinder.last_reached_node should always be present if there's a path", - ), + start: executing_path.last_reached_node, physics, look_at_events: &mut look_at_events, sprint_events: &mut sprint_events, @@ -481,86 +626,74 @@ fn tick_execute_path( trace!("executing move"); (movement.data.execute)(ctx); } + } +} - { - // obstruction check (the path we're executing isn't possible anymore) - let ctx = PathfinderCtx::new(world_lock); - let successors = |pos: BlockPos| { - let mut edges = Vec::with_capacity(16); - successors_fn(&mut edges, &ctx, pos); - edges - }; - - if let Some(last_reached_node) = pathfinder.last_reached_node { - if let Some(obstructed_index) = - check_path_obstructed(last_reached_node, &pathfinder.path, successors) - { - warn!("path obstructed at index {obstructed_index} (starting at {last_reached_node:?}, path: {:?})", pathfinder.path); - pathfinder.path.truncate(obstructed_index); - pathfinder.is_path_partial = true; - } - } - } - - { - // start recalculating if the path ends soon - if (pathfinder.path.len() == 20 || pathfinder.path.len() < 5) - && !pathfinder.is_calculating - && pathfinder.is_path_partial - { - if let Some(goal) = pathfinder.goal.as_ref().cloned() { - debug!("Recalculating path because it ends soon"); - goto_events.send(GotoEvent { - entity, - goal, - successors_fn, - }); - pathfinder.is_calculating = true; - - if pathfinder.path.is_empty() { - if let Some(new_path) = pathfinder.queued_path.take() { - pathfinder.path = new_path; - if pathfinder.path.is_empty() { - info!( - "the path we just swapped to was empty, so reached end of path" - ); - walk_events.send(StartWalkEvent { - entity, - direction: WalkDirection::None, - }); - break; - } - } else { - walk_events.send(StartWalkEvent { - entity, - direction: WalkDirection::None, - }); - } - } - } else if pathfinder.path.is_empty() { - // idk when this can happen but stop moving just in case - walk_events.send(StartWalkEvent { - entity, - direction: WalkDirection::None, - }); - } +fn recalculate_if_has_goal_but_no_path( + mut query: Query<(Entity, &mut Pathfinder), Without>, + mut goto_events: EventWriter, +) { + for (entity, mut pathfinder) in &mut query { + if pathfinder.goal.is_some() && !pathfinder.is_calculating { + if let Some(goal) = pathfinder.goal.as_ref().cloned() { + debug!("Recalculating path because it has a goal but no ExecutingPath"); + goto_events.send(GotoEvent { + entity, + goal, + successors_fn: pathfinder.successors_fn.unwrap(), + }); + pathfinder.is_calculating = true; } } } } -fn stop_pathfinding_on_instance_change( - mut query: Query<(Entity, &mut Pathfinder), Changed>, +#[derive(Event)] +pub struct StopPathfindingEvent { + pub entity: Entity, + /// If false, then let the current movement finish before stopping. If true, + /// then stop moving immediately. This might cause the bot to fall if it was + /// in the middle of parkouring. + pub force: bool, +} + +fn handle_stop_pathfinding_event( + mut events: EventReader, + mut query: Query<(&mut Pathfinder, &mut ExecutingPath)>, mut walk_events: EventWriter, + mut commands: Commands, ) { - for (entity, mut pathfinder) in &mut query { - if !pathfinder.path.is_empty() { - debug!("instance changed, clearing path"); - pathfinder.path.clear(); + for event in events.iter() { + let Ok((mut pathfinder, mut executing_path)) = query.get_mut(event.entity) else { + continue; + }; + pathfinder.goal = None; + if event.force { + executing_path.path.clear(); + executing_path.queued_path = None; walk_events.send(StartWalkEvent { - entity, + entity: event.entity, direction: WalkDirection::None, }); + commands.entity(event.entity).remove::(); + } else { + executing_path.queued_path = Some(VecDeque::new()); + } + } +} + +fn stop_pathfinding_on_instance_change( + mut query: Query<(Entity, &mut ExecutingPath), Changed>, + mut stop_pathfinding_events: EventWriter, +) { + for (entity, mut executing_path) in &mut query { + if !executing_path.path.is_empty() { + debug!("instance changed, clearing path"); + executing_path.path.clear(); + stop_pathfinding_events.send(StopPathfindingEvent { + entity, + force: true, + }); } } } @@ -572,7 +705,7 @@ fn stop_pathfinding_on_instance_change( pub struct PathfinderDebugParticles; fn debug_render_path_with_particles( - mut query: Query<(Entity, &Pathfinder), With>, + mut query: Query<(Entity, &ExecutingPath), With>, // chat_events is Option because the tests don't have SendChatEvent // and we have to use ResMut because bevy doesn't support Option chat_events: Option>>, @@ -587,15 +720,13 @@ fn debug_render_path_with_particles( *tick_count += 1; return; } - for (entity, pathfinder) in &mut query { - if pathfinder.path.is_empty() { + for (entity, executing_path) in &mut query { + if executing_path.path.is_empty() { continue; } - let mut start = pathfinder - .last_reached_node - .unwrap_or_else(|| pathfinder.path.front().unwrap().target); - for movement in &pathfinder.path { + let mut start = executing_path.last_reached_node; + for (i, movement) in executing_path.path.iter().enumerate() { // /particle dust 0 1 1 1 ~ ~ ~ 0 0 0.2 0 100 let end = movement.target; @@ -605,6 +736,8 @@ fn debug_render_path_with_particles( let step_count = (start_vec3.distance_to_sqr(&end_vec3).sqrt() * 4.0) as usize; + let (r, g, b): (f64, f64, f64) = if i == 0 { (0., 1., 0.) } else { (0., 1., 1.) }; + // interpolate between the start and end positions for i in 0..step_count { let percent = i as f64 / step_count as f64; @@ -615,9 +748,6 @@ fn debug_render_path_with_particles( }; let particle_command = format!( "/particle dust {r} {g} {b} {size} {start_x} {start_y} {start_z} {delta_x} {delta_y} {delta_z} 0 {count}", - r = 0, - g = 1, - b = 1, size = 1, start_x = pos.x, start_y = pos.y, @@ -906,4 +1036,28 @@ mod tests { BlockPos::new(3, 74, 0) ); } + + #[test] + fn test_consecutive_3_gap_parkour() { + let mut partial_chunks = PartialChunkStorage::default(); + let mut simulation = setup_simulation( + &mut partial_chunks, + BlockPos::new(0, 71, 0), + BlockPos::new(4, 71, 12), + vec![ + BlockPos::new(0, 70, 0), + BlockPos::new(0, 70, 4), + BlockPos::new(0, 70, 8), + BlockPos::new(0, 70, 12), + BlockPos::new(4, 70, 12), + ], + ); + for _ in 0..80 { + simulation.tick(); + } + assert_eq!( + BlockPos::from(simulation.position()), + BlockPos::new(4, 71, 12) + ); + } } diff --git a/azalea/src/pathfinder/moves/parkour.rs b/azalea/src/pathfinder/moves/parkour.rs index 617333c8..832d0f4a 100644 --- a/azalea/src/pathfinder/moves/parkour.rs +++ b/azalea/src/pathfinder/moves/parkour.rs @@ -3,7 +3,7 @@ use azalea_core::{direction::CardinalDirection, position::BlockPos}; use crate::pathfinder::{astar, costs::*}; -use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; +use super::{Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; pub fn parkour_move(edges: &mut Vec, ctx: &PathfinderCtx, node: BlockPos) { parkour_forward_1_move(edges, ctx, node); @@ -109,7 +109,7 @@ fn parkour_forward_2_move(edges: &mut Vec, ctx: &PathfinderCtx, pos: Block target: pos + offset.up(ascend), data: MoveData { execute: &execute_parkour_move, - is_reached: &default_is_reached, + is_reached: &parkour_is_reached, }, }, cost, @@ -161,7 +161,7 @@ fn parkour_forward_3_move(edges: &mut Vec, ctx: &PathfinderCtx, pos: Block target: pos + offset, data: MoveData { execute: &execute_parkour_move, - is_reached: &default_is_reached, + is_reached: &parkour_is_reached, }, }, cost, @@ -212,8 +212,8 @@ fn execute_parkour_move(mut ctx: ExecuteCtx) { if !is_at_start_block && !is_at_jump_block - && position.y == start.y as f64 - && distance_from_start < 0.8 + && (position.y - start.y as f64) < 0.094 + && distance_from_start < 0.81 { // we have to be on the start block to jump ctx.look_at(start_center); diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 66e5ea23..18c92438 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -390,7 +390,11 @@ where while let Some((Some(event), bot)) = bots_rx.recv().await { if let Some(handler) = &self.handler { let state = bot.component::(); - tokio::spawn((handler)(bot, event, state)); + tokio::spawn((handler)(bot, event, state.clone())); + // this makes it not have to keep locking the ecs + while let Ok((Some(event), bot)) = bots_rx.try_recv() { + tokio::spawn((handler)(bot, event, state.clone())); + } } }