aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2026-03-27 13:49:18 -0600
committermat <git@matdoes.dev>2026-03-28 01:49:34 +0600
commit2d3e4194b885ec499826812da52c965f5a7235cf (patch)
tree9cffc73bb9c5ffa29591392f060816b2c9f321a6
parenteeaf1435e81d9cbd8daa0efa22029c1f259a64b5 (diff)
downloadazalea-drasl-2d3e4194b885ec499826812da52c965f5a7235cf.tar.xz
instant path updates for simple paths, and add follow command to testbot
-rw-r--r--Cargo.lock42
-rw-r--r--azalea-world/src/chunk/mod.rs2
-rw-r--r--azalea-world/src/chunk/partial.rs8
-rw-r--r--azalea/benches/checks.rs2
-rw-r--r--azalea/examples/testbot/commands/movement.rs18
-rw-r--r--azalea/examples/testbot/main.rs36
-rw-r--r--azalea/src/pathfinder/astar/mod.rs5
-rw-r--r--azalea/src/pathfinder/execute/mod.rs27
-rw-r--r--azalea/src/pathfinder/execute/simulation.rs12
-rw-r--r--azalea/src/pathfinder/goto_event.rs8
-rw-r--r--azalea/src/pathfinder/mod.rs166
11 files changed, 234 insertions, 92 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ca1ba56a..72031f9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -235,7 +235,7 @@ dependencies = [
[[package]]
name = "azalea"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-auth",
"azalea-block",
@@ -275,7 +275,7 @@ dependencies = [
[[package]]
name = "azalea-auth"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf",
"azalea-crypto",
@@ -295,7 +295,7 @@ dependencies = [
[[package]]
name = "azalea-block"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-block-macros",
"azalea-buf",
@@ -304,7 +304,7 @@ dependencies = [
[[package]]
name = "azalea-block-macros"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"proc-macro2",
"quote",
@@ -313,7 +313,7 @@ dependencies = [
[[package]]
name = "azalea-brigadier"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf",
"azalea-chat",
@@ -324,7 +324,7 @@ dependencies = [
[[package]]
name = "azalea-buf"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf-macros",
"byteorder",
@@ -338,7 +338,7 @@ dependencies = [
[[package]]
name = "azalea-buf-macros"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"proc-macro2",
"quote",
@@ -347,7 +347,7 @@ dependencies = [
[[package]]
name = "azalea-chat"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf",
"azalea-language",
@@ -360,7 +360,7 @@ dependencies = [
[[package]]
name = "azalea-client"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"async-compat",
"azalea-auth",
@@ -398,7 +398,7 @@ dependencies = [
[[package]]
name = "azalea-core"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf",
"azalea-chat",
@@ -418,7 +418,7 @@ dependencies = [
[[package]]
name = "azalea-crypto"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"aes",
"azalea-buf",
@@ -437,7 +437,7 @@ dependencies = [
[[package]]
name = "azalea-entity"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-block",
"azalea-buf",
@@ -467,7 +467,7 @@ dependencies = [
[[package]]
name = "azalea-inventory"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-auth",
"azalea-buf",
@@ -484,7 +484,7 @@ dependencies = [
[[package]]
name = "azalea-inventory-macros"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"proc-macro2",
"quote",
@@ -493,7 +493,7 @@ dependencies = [
[[package]]
name = "azalea-language"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"compact_str",
"serde_json",
@@ -501,7 +501,7 @@ dependencies = [
[[package]]
name = "azalea-physics"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-block",
"azalea-core",
@@ -518,7 +518,7 @@ dependencies = [
[[package]]
name = "azalea-protocol"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-auth",
"azalea-block",
@@ -555,7 +555,7 @@ dependencies = [
[[package]]
name = "azalea-protocol-macros"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"proc-macro2",
"quote",
@@ -564,7 +564,7 @@ dependencies = [
[[package]]
name = "azalea-registry"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-buf",
"azalea-registry-macros",
@@ -574,7 +574,7 @@ dependencies = [
[[package]]
name = "azalea-registry-macros"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"quote",
"syn",
@@ -582,7 +582,7 @@ dependencies = [
[[package]]
name = "azalea-world"
-version = "0.15.0+mc26.1-rc-2"
+version = "0.15.0+mc26.1"
dependencies = [
"azalea-block",
"azalea-buf",
diff --git a/azalea-world/src/chunk/mod.rs b/azalea-world/src/chunk/mod.rs
index 48cb202c..66b2434b 100644
--- a/azalea-world/src/chunk/mod.rs
+++ b/azalea-world/src/chunk/mod.rs
@@ -26,6 +26,8 @@ const SECTION_HEIGHT: u32 = 16;
/// This only contains blocks and biomes. You can derive the height of the chunk
/// from the number of sections, but you need a [`ChunkStorage`] to get the
/// minimum Y coordinate.
+///
+/// [`ChunkStorage`]: crate::ChunkStorage
#[derive(Debug)]
pub struct Chunk {
pub sections: Box<[Section]>,
diff --git a/azalea-world/src/chunk/partial.rs b/azalea-world/src/chunk/partial.rs
index e0f4912b..e476f075 100644
--- a/azalea-world/src/chunk/partial.rs
+++ b/azalea-world/src/chunk/partial.rs
@@ -140,7 +140,9 @@ impl PartialChunkStorage {
}
/// Get a [`Chunk`] within render distance, or `None` if it's not loaded.
- /// Use [`ChunkStorage::get`] to get a chunk from the shared storage.
+ /// Use [`ChunkStorageTrait::get`] to get a chunk from the shared storage.
+ ///
+ /// [`ChunkStorageTrait::get`]: crate::chunk::storage::ChunkStorageTrait::get
pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
if !self.in_range(pos) {
warn!(
@@ -156,7 +158,9 @@ impl PartialChunkStorage {
/// Get a mutable reference to a [`Chunk`] within render distance, or
/// `None` if it's not loaded.
///
- /// Use [`ChunkStorage::get`] to get a chunk from the shared storage.
+ /// Use [`ChunkStorageTrait::get`] to get a chunk from the shared storage.
+ ///
+ /// [`ChunkStorageTrait::get`]: crate::chunk::storage::ChunkStorageTrait::get
pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
if !self.in_range(pos) {
return None;
diff --git a/azalea/benches/checks.rs b/azalea/benches/checks.rs
index bd1b9085..61f1f07d 100644
--- a/azalea/benches/checks.rs
+++ b/azalea/benches/checks.rs
@@ -23,7 +23,7 @@ fn benchmark(c: &mut Criterion) {
});
let waterlogged_slab = azalea_block::blocks::OakSlab {
- kind: azalea_block::properties::Type::Bottom,
+ kind: azalea_block::properties::SlabKind::Bottom,
waterlogged: true,
}
.into();
diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs
index 3f015f2c..500e17b0 100644
--- a/azalea/examples/testbot/commands/movement.rs
+++ b/azalea/examples/testbot/commands/movement.rs
@@ -9,7 +9,6 @@ use azalea::{
use parking_lot::Mutex;
use super::{CommandSource, Ctx};
-use crate::BotTask;
pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
commands.register(
@@ -72,6 +71,19 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
))),
);
+ commands.register(literal("follow").executes(|ctx: &Ctx| {
+ let source = ctx.source.lock();
+ println!("got follow");
+ // look for the sender
+ let Some(entity) = source.entity() else {
+ source.reply("I can't see you!");
+ return 0;
+ };
+ source.reply("ok");
+ *source.state.following_entity.lock() = Some(entity);
+ 1
+ }));
+
commands.register(literal("down").executes(|ctx: &Ctx| {
let source = ctx.source.clone();
tokio::spawn(async move {
@@ -207,14 +219,14 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
let source = ctx.source.lock();
source.bot.stop_pathfinding();
source.reply("ok");
- *source.state.task.lock() = BotTask::None;
+ *source.state.following_entity.lock() = None;
1
}));
commands.register(literal("forcestop").executes(|ctx: &Ctx| {
let source = ctx.source.lock();
source.bot.force_stop_pathfinding();
source.reply("ok");
- *source.state.task.lock() = BotTask::None;
+ *source.state.following_entity.lock() = None;
1
}));
}
diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs
index 57bdcc72..173f2b8c 100644
--- a/azalea/examples/testbot/main.rs
+++ b/azalea/examples/testbot/main.rs
@@ -29,11 +29,14 @@ pub mod mspt;
use std::{env, process, sync::Arc, thread, time::Duration};
use azalea::{
- ClientInformation,
+ ClientInformation, EntityRef,
brigadier::command_dispatcher::CommandDispatcher,
ecs::prelude::*,
pathfinder::{
- debug::PathfinderDebugParticles, execute::simulation::SimulationPathfinderExecutionPlugin,
+ PathfinderOpts,
+ debug::PathfinderDebugParticles,
+ execute::simulation::SimulationPathfinderExecutionPlugin,
+ goals::{Goal, RadiusGoal},
},
prelude::*,
swarm::prelude::*,
@@ -105,23 +108,17 @@ fn deadlock_detection_thread() {
}
}
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum BotTask {
- #[default]
- None,
-}
-
#[derive(Clone, Component, Default)]
pub struct State {
pub killaura: bool,
- pub task: Arc<Mutex<BotTask>>,
+ pub following_entity: Arc<Mutex<Option<EntityRef>>>,
}
impl State {
fn new() -> Self {
Self {
killaura: false,
- task: Arc::new(Mutex::new(BotTask::None)),
+ following_entity: Default::default(),
}
}
}
@@ -188,9 +185,22 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result
azalea::Event::Tick => {
killaura::tick(bot.clone(), state.clone())?;
- let task = *state.task.lock();
- match task {
- BotTask::None => {}
+ if bot.ticks_connected().is_multiple_of(5) {
+ if let Some(following) = &*state.following_entity.lock() {
+ let goal = RadiusGoal::new(following.position(), 3.);
+ if bot.is_calculating_path() {
+ // keep waiting
+ } else if !goal.success(bot.position().into()) || bot.is_executing_path() {
+ bot.start_goto_with_opts(
+ goal,
+ PathfinderOpts::new()
+ .retry_on_no_path(false)
+ .max_timeout(Duration::from_secs(1)),
+ );
+ } else {
+ following.look_at();
+ }
+ }
}
}
azalea::Event::Login => {
diff --git a/azalea/src/pathfinder/astar/mod.rs b/azalea/src/pathfinder/astar/mod.rs
index e6118bc0..077ce13a 100644
--- a/azalea/src/pathfinder/astar/mod.rs
+++ b/azalea/src/pathfinder/astar/mod.rs
@@ -313,3 +313,8 @@ impl Default for PathfinderTimeout {
Self::Time(Duration::from_secs(1))
}
}
+impl From<Duration> for PathfinderTimeout {
+ fn from(duration: Duration) -> Self {
+ Self::Time(duration)
+ }
+}
diff --git a/azalea/src/pathfinder/execute/mod.rs b/azalea/src/pathfinder/execute/mod.rs
index f4214054..a06d1c0f 100644
--- a/azalea/src/pathfinder/execute/mod.rs
+++ b/azalea/src/pathfinder/execute/mod.rs
@@ -157,7 +157,10 @@ pub fn check_node_reached(
position: **position,
physics,
};
- let extra_check = if i == executing_path.path.len() - 1 {
+ let extra_check = if i == executing_path.path.len() - 1
+ // only do the extra check if we don't have a new path immediately queued up
+ && executing_path.is_empty_queued_path()
+ {
// be extra strict about the velocity and centering if we're on the last node so
// we don't fall off
@@ -257,7 +260,7 @@ pub fn timeout_movement(
&WorldName,
&Inventory,
Option<&CustomPathfinderState>,
- Option<&SimulatingPathState>,
+ Option<&mut SimulatingPathState>,
)>,
worlds: Res<Worlds>,
) {
@@ -274,8 +277,8 @@ pub fn timeout_movement(
) in &mut query
{
if !executing_path.path.is_empty() {
- let (start, end) = if let Some(SimulatingPathState::Simulated(simulating_path_state)) =
- simulating_path_state
+ let (start, end) = if let Some(s) = &simulating_path_state
+ && let SimulatingPathState::Simulated(simulating_path_state) = &**s
{
(simulating_path_state.start, simulating_path_state.target)
} else {
@@ -302,6 +305,12 @@ pub fn timeout_movement(
"pathfinder went too far from path (xz_distance={xz_distance}/{xz_tolerance}, y_distance={y_distance}/{y_tolerance}, line is {start} to {end}, point at {}), trying to patch!",
**position
);
+
+ if let Some(mut simulating_path_state) = simulating_path_state {
+ // don't keep executing the simulation
+ *simulating_path_state = SimulatingPathState::Fail;
+ }
+
patch_path_from_timeout(
entity,
&mut executing_path,
@@ -410,7 +419,11 @@ pub fn recalculate_near_end_of_path(
continue;
};
- // start recalculating if the path ends soon
+ // start recalculating if the path ends soon. 50 is arbitrary, that's just to
+ // make us recalculate once when we start nearing the end. this doesn't account
+ // for skipping nodes, though...
+ // TODO: have a variable to store whether we've recalculated, and then check
+ // that `&& path.len() <= 50` to see if we should recalculate.
if (executing_path.path.len() == 50 || executing_path.path.len() < 5)
&& !pathfinder.is_calculating
&& executing_path.is_path_partial
@@ -513,7 +526,9 @@ pub fn point_line_distance_1d(point: f64, (start, end): (f64, f64)) -> f64 {
let max = start.max(end);
if point < min {
min - point
- } else {
+ } else if point > max {
point - max
+ } else {
+ 0.
}
}
diff --git a/azalea/src/pathfinder/execute/simulation.rs b/azalea/src/pathfinder/execute/simulation.rs
index 7287587b..1b010cca 100644
--- a/azalea/src/pathfinder/execute/simulation.rs
+++ b/azalea/src/pathfinder/execute/simulation.rs
@@ -79,7 +79,7 @@ pub enum SimulatingPathState {
Fail,
Simulated(SimulatingPathOpts),
}
-#[derive(Clone, Component, Debug)]
+#[derive(Clone, Debug)]
pub struct SimulatingPathOpts {
pub start: BlockPos,
pub target: BlockPos,
@@ -89,6 +89,14 @@ pub struct SimulatingPathOpts {
pub sprinting: bool,
pub y_rot: f32,
}
+impl SimulatingPathState {
+ pub fn as_simulated(&self) -> Option<&SimulatingPathOpts> {
+ match self {
+ Self::Fail => None,
+ Self::Simulated(s) => Some(s),
+ }
+ }
+}
#[allow(clippy::type_complexity)]
pub fn tick_execute_path(
@@ -249,6 +257,8 @@ fn run_simulations(
let mut sim = Simulation::new(world_holder.shared.read().chunks.clone(), player.clone());
+ // note that we can't skip more than 50 nodes without causing issues with the
+ // executing_path_limit in goto_listener
for nodes_ahead in [20, 15, 10, 5, 4, 3, 2, 1, 0] {
if nodes_ahead + 1 >= executing_path.path.len() {
// don't simulate to the last node since it has stricter checks
diff --git a/azalea/src/pathfinder/goto_event.rs b/azalea/src/pathfinder/goto_event.rs
index e4f934f2..7d112f28 100644
--- a/azalea/src/pathfinder/goto_event.rs
+++ b/azalea/src/pathfinder/goto_event.rs
@@ -101,8 +101,8 @@ impl PathfinderOpts {
/// Defaults to `PathfinderTimeout::Time(Duration::from_secs(1))`.
///
/// Also see [`PathfinderTimeout::Nodes`]
- pub fn min_timeout(mut self, min_timeout: PathfinderTimeout) -> Self {
- self.min_timeout = min_timeout;
+ pub fn min_timeout(mut self, min_timeout: impl Into<PathfinderTimeout>) -> Self {
+ self.min_timeout = min_timeout.into();
self
}
/// The absolute maximum amount of time that the pathfinder function can
@@ -112,8 +112,8 @@ impl PathfinderOpts {
/// impossible).
///
/// Defaults to `PathfinderTimeout::Time(Duration::from_secs(5))`.
- pub fn max_timeout(mut self, max_timeout: PathfinderTimeout) -> Self {
- self.max_timeout = max_timeout;
+ pub fn max_timeout(mut self, max_timeout: impl Into<PathfinderTimeout>) -> Self {
+ self.max_timeout = max_timeout.into();
self
}
}
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 0269389b..c577b8e3 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -70,7 +70,9 @@ use crate::{
system::{Commands, Query, Res},
},
pathfinder::{
- astar::a_star, execute::DefaultPathfinderExecutionPlugin, moves::MovesCtx,
+ astar::{PathfinderTimeout, a_star},
+ execute::{DefaultPathfinderExecutionPlugin, simulation::SimulatingPathState},
+ moves::MovesCtx,
world::CachedWorld,
},
};
@@ -130,6 +132,11 @@ pub struct ExecutingPath {
pub ticks_since_last_node_reached: usize,
pub is_path_partial: bool,
}
+impl ExecutingPath {
+ pub fn is_empty_queued_path(&self) -> bool {
+ self.queued_path.is_none() || self.queued_path.as_ref().is_some_and(|p| p.is_empty())
+ }
+}
#[derive(Clone, Debug, Message)]
#[non_exhaustive]
@@ -293,9 +300,11 @@ pub struct ComputePath(Task<Option<PathFoundEvent>>);
pub fn goto_listener(
mut commands: Commands,
mut events: MessageReader<GotoEvent>,
+ mut path_found_events: MessageWriter<PathFoundEvent>,
mut query: Query<(
&mut Pathfinder,
Option<&mut ExecutingPath>,
+ Option<&SimulatingPathState>,
&Position,
&WorldName,
&Inventory,
@@ -306,8 +315,15 @@ pub fn goto_listener(
let thread_pool = AsyncComputeTaskPool::get();
for event in events.read() {
- let Ok((mut pathfinder, executing_path, position, world_name, inventory, custom_state)) =
- query.get_mut(event.entity)
+ let Ok((
+ mut pathfinder,
+ executing_path,
+ simulating_path_state,
+ position,
+ world_name,
+ inventory,
+ custom_state,
+ )) = query.get_mut(event.entity)
else {
warn!("got goto event for an entity that can't pathfind");
continue;
@@ -339,34 +355,6 @@ pub fn goto_listener(
pathfinder.opts = Some(event.opts.clone());
pathfinder.is_calculating = true;
- let start = if let Some(mut executing_path) = executing_path
- && { !executing_path.path.is_empty() }
- {
- // if we're currently pathfinding and got a goto event, start a little ahead
-
- let executing_path_limit = 50;
- // truncate the executing path so we can cleanly combine the two paths later
- executing_path.path.truncate(executing_path_limit);
-
- executing_path
- .path
- .back()
- .expect("path was just checked to not be empty")
- .movement
- .target
- } else {
- cur_pos
- };
-
- if start == cur_pos {
- info!("got goto {:?}, starting from {start:?}", event.goal);
- } else {
- info!(
- "got goto {:?}, starting from {start:?} (currently at {cur_pos:?})",
- event.goal,
- );
- }
-
let world_lock = worlds
.get(world_name)
.expect("Entity tried to pathfind but the entity isn't in a valid world");
@@ -377,14 +365,104 @@ pub fn goto_listener(
let goto_id_atomic = pathfinder.goto_id.clone();
let allow_mining = event.opts.allow_mining;
- let mining_cache = MiningCache::new(if allow_mining {
+ let inventory_menu = if allow_mining {
Some(inventory.inventory_menu.clone())
} else {
None
- });
+ };
let custom_state = custom_state.cloned().unwrap_or_default();
let opts = event.opts.clone();
+
+ // if we're executing a path, this might get replaced with something else
+ let mut start = cur_pos;
+
+ if let Some(mut executing_path) = executing_path {
+ // first try calculating the path instantly, which allows us to react quickly
+ // for easy paths (but we'll fall back to spawning a thread if this fails)
+
+ // first, try starting at the node that we're going to
+ let instant_path_start = simulating_path_state
+ .and_then(|s| s.as_simulated().map(|s| s.target))
+ .unwrap_or_else(|| {
+ executing_path
+ .path
+ .iter()
+ .next()
+ .map(|e| e.movement.target)
+ .unwrap_or(cur_pos)
+ });
+
+ let path_found_event = calculate_path(CalculatePathCtx {
+ entity,
+ start: instant_path_start,
+ goal: goal.clone(),
+ world_lock: world_lock.clone(),
+ goto_id_atomic: goto_id_atomic.clone(),
+ mining_cache: MiningCache::new(inventory_menu.clone()),
+ custom_state: custom_state.clone(),
+ opts: PathfinderOpts {
+ min_timeout: PathfinderTimeout::Nodes(2_000),
+ max_timeout: PathfinderTimeout::Nodes(2_000),
+ ..opts
+ },
+ });
+
+ if let Some(path_found_event) = path_found_event
+ && !path_found_event.is_partial
+ {
+ debug!("Found path instantly!");
+
+ // instant_path_start needs to be equal to executing_path.path.back() for the
+ // path merging in path_found_listener to work correctly
+ let instant_path_start_index = executing_path
+ .path
+ .iter()
+ .position(|e| e.movement.target == instant_path_start);
+ if let Some(instant_path_start_index) = instant_path_start_index {
+ let truncate_to_len = instant_path_start_index + 1;
+ debug!("truncating to {truncate_to_len} for instant path");
+ executing_path.path.truncate(truncate_to_len);
+
+ path_found_events.write(path_found_event);
+
+ // we found the path instantly, so we're done here :)
+ continue;
+ } else {
+ warn!(
+ "we just calculated an instant path, but the start of it isn't in the current path? instant_path_start: {instant_path_start:?}, simulating_path_state: {simulating_path_state:?}, executing_path.path: {:?}",
+ executing_path.path
+ )
+ }
+ }
+
+ if !executing_path.path.is_empty() {
+ // if we're currently pathfinding and got a goto event, start a little ahead
+
+ let executing_path_limit = 50;
+
+ // truncate the executing path so we can cleanly combine the two paths later
+ executing_path.path.truncate(executing_path_limit);
+
+ start = executing_path
+ .path
+ .back()
+ .expect("path was just checked to not be empty")
+ .movement
+ .target;
+ }
+ }
+
+ if start == cur_pos {
+ info!("got goto {:?}, starting from {start:?}", event.goal);
+ } else {
+ info!(
+ "got goto {:?}, starting from {start:?} (currently at {cur_pos:?})",
+ event.goal,
+ );
+ }
+
+ let mining_cache = MiningCache::new(inventory_menu);
let task = thread_pool.spawn(async move {
calculate_path(CalculatePathCtx {
entity,
@@ -434,7 +512,7 @@ pub struct CalculatePathCtx {
/// calling this function. `None` will be returned if the pathfinding was
/// interrupted by another path calculation.
pub fn calculate_path(ctx: CalculatePathCtx) -> Option<PathFoundEvent> {
- debug!("start: {:?}", ctx.start);
+ debug!("start: {}", ctx.start);
let goto_id = ctx.goto_id_atomic.fetch_add(1, atomic::Ordering::SeqCst) + 1;
@@ -573,7 +651,7 @@ pub fn path_found_listener(
debug!("got path found event for an entity that can't pathfind");
continue;
};
- if let Some(path) = &event.path {
+ if let Some(found_path) = &event.path {
if let Some(mut executing_path) = executing_path {
let mut new_path = VecDeque::new();
@@ -603,7 +681,7 @@ pub fn path_found_listener(
)
};
- if let Some(first_node_of_new_path) = path.front() {
+ if let Some(first_node_of_new_path) = found_path.front() {
let last_target_of_current_path = RelBlockPos::from_origin(
origin,
last_node_of_current_path.movement.target,
@@ -622,7 +700,10 @@ pub fn path_found_listener(
"old path: {:?}",
executing_path.path.iter().collect::<Vec<_>>()
);
- debug!("new path: {:?}", path.iter().take(10).collect::<Vec<_>>());
+ debug!(
+ "new path: {:?}",
+ found_path.iter().take(10).collect::<Vec<_>>()
+ );
new_path.extend(executing_path.path.iter().cloned());
}
} else {
@@ -630,7 +711,7 @@ pub fn path_found_listener(
}
}
- new_path.extend(path.to_owned());
+ new_path.extend(found_path.to_owned());
debug!(
"set queued path to {:?}",
@@ -638,7 +719,7 @@ pub fn path_found_listener(
);
executing_path.queued_path = Some(new_path);
executing_path.is_path_partial = event.is_partial;
- } else if path.is_empty() {
+ } else if found_path.is_empty() {
debug!("calculated path is empty, so didn't add ExecutingPath");
if !pathfinder.opts.as_ref().is_some_and(|o| o.retry_on_no_path) {
debug!("retry_on_no_path is set to false, removing goal");
@@ -646,13 +727,16 @@ pub fn path_found_listener(
}
} else {
commands.entity(event.entity).insert(ExecutingPath {
- path: path.to_owned(),
+ path: found_path.to_owned(),
queued_path: None,
last_reached_node: event.start,
ticks_since_last_node_reached: 0,
is_path_partial: event.is_partial,
});
- debug!("set path to {:?}", path.iter().take(10).collect::<Vec<_>>());
+ debug!(
+ "set path to {:?}",
+ found_path.iter().take(10).collect::<Vec<_>>()
+ );
debug!("partial: {}", event.is_partial);
}
} else {