From 9a687f0ffebad80441e036aabe14e7cf80c774d3 Mon Sep 17 00:00:00 2001 From: mat Date: Tue, 10 Oct 2023 23:21:23 -0500 Subject: [PATCH] start adding mining to pathfinder --- .../src/arguments/argument_type.rs | 4 +- .../src/arguments/bool_argument_type.rs | 6 +- .../src/arguments/double_argument_type.rs | 6 +- .../src/arguments/float_argument_type.rs | 6 +- .../src/arguments/integer_argument_type.rs | 6 +- .../src/arguments/long_argument_type.rs | 6 +- .../src/arguments/string_argument_type.rs | 6 +- .../src/builder/required_argument_builder.rs | 4 +- .../src/context/command_context.rs | 2 +- .../src/context/parsed_argument.rs | 4 +- azalea-client/src/inventory.rs | 2 +- azalea-core/src/bitset.rs | 5 ++ azalea-entity/src/lib.rs | 6 ++ azalea-entity/src/mining.rs | 18 ++-- azalea/benches/pathfinder.rs | 10 ++- azalea/src/auto_tool.rs | 84 +++++++++++++++++++ azalea/src/bot.rs | 3 +- azalea/src/container.rs | 4 + azalea/src/lib.rs | 1 + azalea/src/pathfinder/mining.rs | 30 +++++++ azalea/src/pathfinder/mod.rs | 52 +++++++++--- azalea/src/pathfinder/moves/mod.rs | 3 +- azalea/src/pathfinder/simulation.rs | 4 +- 23 files changed, 221 insertions(+), 51 deletions(-) create mode 100644 azalea/src/auto_tool.rs create mode 100644 azalea/src/pathfinder/mining.rs diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs index 029e4696..f44233e1 100755 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -1,7 +1,7 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{exceptions::CommandSyntaxException, string_reader::StringReader}; pub trait ArgumentType { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException>; + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException>; } diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs index 1bff8aa3..2e348c7b 100644 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader, @@ -10,8 +10,8 @@ use super::ArgumentType; struct Boolean; impl ArgumentType for Boolean { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { - Ok(Rc::new(reader.read_boolean()?)) + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + Ok(Arc::new(reader.read_boolean()?)) } } diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs index d83bfb2a..9502a680 100644 --- a/azalea-brigadier/src/arguments/double_argument_type.rs +++ b/azalea-brigadier/src/arguments/double_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, @@ -15,7 +15,7 @@ struct Double { } impl ArgumentType for Double { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { let start = reader.cursor; let result = reader.read_double()?; if let Some(minimum) = self.minimum { @@ -38,7 +38,7 @@ impl ArgumentType for Double { .create_with_context(reader)); } } - Ok(Rc::new(result)) + Ok(Arc::new(result)) } } diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs index 0d194efe..a2831a08 100644 --- a/azalea-brigadier/src/arguments/float_argument_type.rs +++ b/azalea-brigadier/src/arguments/float_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, @@ -15,7 +15,7 @@ struct Float { } impl ArgumentType for Float { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { let start = reader.cursor; let result = reader.read_float()?; if let Some(minimum) = self.minimum { @@ -38,7 +38,7 @@ impl ArgumentType for Float { .create_with_context(reader)); } } - Ok(Rc::new(result)) + Ok(Arc::new(result)) } } diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs index 336046a7..a31a6e70 100644 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, @@ -15,7 +15,7 @@ struct Integer { } impl ArgumentType for Integer { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { let start = reader.cursor; let result = reader.read_int()?; if let Some(minimum) = self.minimum { @@ -38,7 +38,7 @@ impl ArgumentType for Integer { .create_with_context(reader)); } } - Ok(Rc::new(result)) + Ok(Arc::new(result)) } } diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs index 9b3469b6..d557881a 100644 --- a/azalea-brigadier/src/arguments/long_argument_type.rs +++ b/azalea-brigadier/src/arguments/long_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, @@ -15,7 +15,7 @@ struct Long { } impl ArgumentType for Long { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { let start = reader.cursor; let result = reader.read_long()?; if let Some(minimum) = self.minimum { @@ -38,7 +38,7 @@ impl ArgumentType for Long { .create_with_context(reader)); } } - Ok(Rc::new(result)) + Ok(Arc::new(result)) } } diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs index 27363bd4..9fd70d13 100644 --- a/azalea-brigadier/src/arguments/string_argument_type.rs +++ b/azalea-brigadier/src/arguments/string_argument_type.rs @@ -1,4 +1,4 @@ -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; use crate::{ context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader, @@ -17,7 +17,7 @@ pub enum StringArgument { } impl ArgumentType for StringArgument { - fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { let result = match self { StringArgument::SingleWord => reader.read_unquoted_string().to_string(), StringArgument::QuotablePhrase => reader.read_string()?, @@ -27,7 +27,7 @@ impl ArgumentType for StringArgument { text } }; - Ok(Rc::new(result)) + Ok(Arc::new(result)) } } diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 0363d204..60fa713f 100755 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -2,7 +2,7 @@ use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; use crate::{ arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader, }; -use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc}; +use std::{any::Any, fmt::Debug, sync::Arc}; /// An argument node type. The `T` type parameter is the type of the argument, /// which can be anything. @@ -19,7 +19,7 @@ impl Argument { } } - pub fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + pub fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { self.parser.parse(reader) } } diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs index a8efeeeb..f78fe758 100755 --- a/azalea-brigadier/src/context/command_context.rs +++ b/azalea-brigadier/src/context/command_context.rs @@ -78,7 +78,7 @@ impl CommandContext { !self.nodes.is_empty() } - pub fn argument(&self, name: &str) -> Option> { + pub fn argument(&self, name: &str) -> Option> { let argument = self.arguments.get(name); argument.map(|a| a.result.clone()) } diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs index 3302b1be..428851a8 100755 --- a/azalea-brigadier/src/context/parsed_argument.rs +++ b/azalea-brigadier/src/context/parsed_argument.rs @@ -1,8 +1,8 @@ use super::string_range::StringRange; -use std::{any::Any, rc::Rc}; +use std::{any::Any, sync::Arc}; #[derive(Clone)] pub struct ParsedArgument { pub range: StringRange, - pub result: Rc, + pub result: Arc, } diff --git a/azalea-client/src/inventory.rs b/azalea-client/src/inventory.rs index 2e8478d4..0e4b9da3 100644 --- a/azalea-client/src/inventory.rs +++ b/azalea-client/src/inventory.rs @@ -69,7 +69,7 @@ impl Client { } /// A component present on all local players that have an inventory. -#[derive(Component, Debug)] +#[derive(Component, Debug, Clone)] pub struct InventoryComponent { /// A component that contains the player's inventory menu. This is /// guaranteed to be a `Menu::Player`. diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs index 912216a8..52a34154 100755 --- a/azalea-core/src/bitset.rs +++ b/azalea-core/src/bitset.rs @@ -125,6 +125,11 @@ impl From> for BitSet { } /// A list of bits with a known fixed size. +/// +/// Note that this is primarily meant for fast serialization and deserialization +/// Minecraft, if you don't need that you should use the `fixedbitset` crate +/// since it's approximately 20% faster (since it stores the data as usizes +/// instead of u8s) #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FixedBitSet where diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 45213fc1..9b673945 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -366,6 +366,12 @@ pub struct LocalEntity; #[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] pub struct FluidOnEyes(azalea_registry::Fluid); +impl FluidOnEyes { + pub fn new(fluid: azalea_registry::Fluid) -> Self { + Self(fluid) + } +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/azalea-entity/src/mining.rs b/azalea-entity/src/mining.rs index a2f869cf..ee5c961b 100644 --- a/azalea-entity/src/mining.rs +++ b/azalea-entity/src/mining.rs @@ -4,6 +4,14 @@ use azalea_registry as registry; use crate::{effects, enchantments, FluidOnEyes, Physics}; +/// How much progress is made towards mining the block per tick, as a +/// percentage. If this is 1 then the block gets broken instantly. +/// +/// You can divide 1 by this and then round up to get the number of ticks it +/// takes to mine the block. +/// +/// The player inventory is needed to check your armor and offhand for modifiers +/// to your mining speed. pub fn get_mine_progress( block: &dyn Block, held_item: registry::Item, @@ -11,16 +19,6 @@ pub fn get_mine_progress( fluid_on_eyes: &FluidOnEyes, physics: &Physics, ) -> f32 { - // public float getDestroyProgress(BlockState blockState, Player player, - // BlockGetter world, BlockPos blockPos) { float destroySpeed = - // blockState.getDestroySpeed(world, blockPos); if (destroySpeed == - // -1.0F) { return 0.0F; - // } else { - // int divider = player.hasCorrectToolForDrops(blockState) ? 30 : 100; - // return player.getDestroySpeed(blockState) / destroySpeed / - // (float)divider; } - // } - let block_behavior: BlockBehavior = block.behavior(); let destroy_time = block_behavior.destroy_time; diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index 3fb1c7b2..406b726d 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -4,12 +4,14 @@ use azalea::{ pathfinder::{ astar::{self, a_star}, goals::BlockPosGoal, + mining::MiningCache, world::CachedWorld, Goal, }, BlockPos, }; use azalea_core::position::{ChunkBlockPos, ChunkPos}; +use azalea_inventory::Menu; use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; use criterion::{criterion_group, criterion_main, Criterion}; use parking_lot::RwLock; @@ -80,10 +82,16 @@ fn bench_pathfinder(c: &mut Criterion) { b.iter(|| { let (world, start, end) = generate_bedrock_world(&mut partial_chunks, 4); let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.into()))); + let mining_cache = MiningCache::new(Menu::Player(azalea_inventory::Player::default())); let goal = BlockPosGoal(end); let successors = |pos: BlockPos| { - azalea::pathfinder::call_successors_fn(&cached_world, successors_fn, pos) + azalea::pathfinder::call_successors_fn( + &cached_world, + &mining_cache, + successors_fn, + pos, + ) }; let astar::Path { movements, partial } = a_star( diff --git a/azalea/src/auto_tool.rs b/azalea/src/auto_tool.rs new file mode 100644 index 00000000..718ed6a7 --- /dev/null +++ b/azalea/src/auto_tool.rs @@ -0,0 +1,84 @@ +use azalea_block::{Block, BlockState}; +use azalea_client::{inventory::InventoryComponent, Client}; +use azalea_entity::{FluidOnEyes, Physics}; +use azalea_inventory::{ItemSlot, Menu}; +use azalea_registry::Fluid; + +pub struct BestToolResult { + pub index: usize, + pub percentage_per_tick: f32, +} + +pub trait AutoToolClientExt { + fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult; +} + +impl AutoToolClientExt for Client { + fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult { + let mut ecs = self.ecs.lock(); + let (inventory, physics, fluid_on_eyes) = + self.query::<(&InventoryComponent, &Physics, &FluidOnEyes)>(&mut ecs); + let menu = &inventory.inventory_menu; + + accurate_best_tool_in_hotbar_for_block(block, menu, physics, fluid_on_eyes) + } +} + +/// Returns the best tool in the hotbar for the given block. +/// +/// Note that this doesn't take into account whether the player is on the ground +/// or in water, use [`accurate_best_tool_in_hotbar_for_block`] instead if you +/// care about those things. +pub fn best_tool_in_hotbar_for_block(block: BlockState, menu: &Menu) -> BestToolResult { + accurate_best_tool_in_hotbar_for_block( + block, + menu, + &Physics { + on_ground: true, + delta: Default::default(), + xxa: Default::default(), + yya: Default::default(), + zza: Default::default(), + last_on_ground: Default::default(), + dimensions: Default::default(), + bounding_box: Default::default(), + has_impulse: Default::default(), + }, + &FluidOnEyes::new(Fluid::Empty), + ) +} + +pub fn accurate_best_tool_in_hotbar_for_block( + block: BlockState, + menu: &Menu, + physics: &Physics, + fluid_on_eyes: &FluidOnEyes, +) -> BestToolResult { + let hotbar_slots = &menu.slots()[menu.hotbar_slots_range()]; + + let mut best_speed = 0.; + let mut best_slot = None; + + let block = Box::::from(block); + + for (i, item_slot) in hotbar_slots.iter().enumerate() { + if let ItemSlot::Present(item_slot) = item_slot { + let this_item_speed = azalea_entity::mining::get_mine_progress( + block.as_ref(), + item_slot.kind, + &menu, + fluid_on_eyes, + physics, + ); + if this_item_speed > best_speed { + best_slot = Some(i); + best_speed = this_item_speed; + } + } + } + + BestToolResult { + index: best_slot.unwrap_or(0), + percentage_per_tick: best_speed, + } +} diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 352eda59..768ae767 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -44,7 +44,8 @@ impl Plugin for BotPlugin { } } -/// Component for all bots. +/// A component that clients with [`BotPlugin`] will have. If you just want to +/// check if an entity is one of our bots, you should use [`LocalEntity`]. #[derive(Default, Component)] pub struct Bot { jumping_once: bool, diff --git a/azalea/src/container.rs b/azalea/src/container.rs index ef6fdcf6..34f86715 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -79,6 +79,10 @@ impl ContainerClientExt for Client { /// Note that this will send a packet to the server once it's dropped. Also, /// due to how it's implemented, you could call this function multiple times /// while another inventory handle already exists (but you shouldn't). + /// + /// If you just want to get the items in the player's inventory without + /// sending any packets, use [`Client::menu`], [`Menu::player_slots_range`], + /// and [`Menu::slots`]. fn open_inventory(&mut self) -> Option { let ecs = self.ecs.lock(); let inventory = ecs diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 3b9d3e6f..c788434b 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -8,6 +8,7 @@ pub mod accept_resource_packs; mod auto_respawn; +pub mod auto_tool; mod bot; pub mod container; pub mod nearest_entity; diff --git a/azalea/src/pathfinder/mining.rs b/azalea/src/pathfinder/mining.rs new file mode 100644 index 00000000..d5977973 --- /dev/null +++ b/azalea/src/pathfinder/mining.rs @@ -0,0 +1,30 @@ +use azalea_block::BlockState; +use azalea_inventory::Menu; +use nohash_hasher::IntMap; + +use crate::auto_tool::best_tool_in_hotbar_for_block; + +pub struct MiningCache { + block_state_id_costs: IntMap, + inventory_menu: Menu, +} + +impl MiningCache { + pub fn new(inventory_menu: Menu) -> Self { + Self { + block_state_id_costs: IntMap::default(), + inventory_menu, + } + } + + pub fn cost_for(&mut self, block: BlockState) -> f32 { + if let Some(cost) = self.block_state_id_costs.get(&block.id) { + *cost + } else { + let best_tool_result = best_tool_in_hotbar_for_block(block, &self.inventory_menu); + let cost = 1. / best_tool_result.percentage_per_tick; + self.block_state_id_costs.insert(block.id, cost); + cost + } + } +} diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index e92457b8..73ce2967 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -4,6 +4,7 @@ pub mod astar; pub mod costs; pub mod goals; +pub mod mining; pub mod moves; pub mod simulation; pub mod world; @@ -23,6 +24,7 @@ use crate::ecs::{ use crate::pathfinder::moves::PathfinderCtx; use crate::pathfinder::world::CachedWorld; use azalea_client::chat::SendChatEvent; +use azalea_client::inventory::{InventoryComponent, InventorySet}; use azalea_client::movement::walk_listener; use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_core::position::{BlockPos, Vec3}; @@ -45,6 +47,7 @@ use std::sync::atomic::{self, AtomicUsize}; use std::sync::Arc; use std::time::{Duration, Instant}; +use self::mining::MiningCache; use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}; #[derive(Clone, Default)] @@ -82,7 +85,8 @@ impl Plugin for PathfinderPlugin { handle_stop_pathfinding_event, ) .chain() - .before(walk_listener), + .before(walk_listener) + .before(InventorySet), ); } } @@ -116,7 +120,7 @@ pub struct GotoEvent { /// `pathfinder::moves::default_move` pub successors_fn: SuccessorsFn, } -#[derive(Event)] +#[derive(Event, Clone)] pub struct PathFoundEvent { pub entity: Entity, pub start: BlockPos, @@ -175,13 +179,14 @@ fn goto_listener( Option<&ExecutingPath>, &Position, &InstanceName, + &InventoryComponent, )>, instance_container: Res, ) { let thread_pool = AsyncComputeTaskPool::get(); for event in events.iter() { - let (mut pathfinder, executing_path, position, instance_name) = query + let (mut pathfinder, executing_path, position, instance_name, inventory) = query .get_mut(event.entity) .expect("Called goto on an entity that's not in the world"); @@ -217,12 +222,15 @@ fn goto_listener( let goto_id_atomic = pathfinder.goto_id.clone(); let goto_id = goto_id_atomic.fetch_add(1, atomic::Ordering::Relaxed) + 1; + let mining_cache = MiningCache::new(inventory.inventory_menu.clone()); let task = thread_pool.spawn(async move { debug!("start: {start:?}"); let cached_world = CachedWorld::new(world_lock); - let successors = |pos: BlockPos| call_successors_fn(&cached_world, successors_fn, pos); + let successors = |pos: BlockPos| { + call_successors_fn(&cached_world, &mining_cache, successors_fn, pos) + }; let mut attempt_number = 0; @@ -311,12 +319,17 @@ 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, Option<&mut ExecutingPath>, &InstanceName)>, + mut query: Query<( + &mut Pathfinder, + Option<&mut ExecutingPath>, + &InstanceName, + &InventoryComponent, + )>, instance_container: Res, mut commands: Commands, ) { for event in events.iter() { - let (mut pathfinder, executing_path, instance_name) = query + let (mut pathfinder, executing_path, instance_name, inventory) = query .get_mut(event.entity) .expect("Path found for an entity that doesn't have a pathfinder"); if let Some(path) = &event.path { @@ -331,8 +344,10 @@ fn path_found_listener( .expect("Entity tried to pathfind but the entity isn't in a valid world"); let successors_fn: moves::SuccessorsFn = event.successors_fn; let cached_world = CachedWorld::new(world_lock); - let successors = - |pos: BlockPos| call_successors_fn(&cached_world, successors_fn, pos); + let mining_cache = MiningCache::new(inventory.inventory_menu.clone()); + let successors = |pos: BlockPos| { + call_successors_fn(&cached_world, &mining_cache, successors_fn, pos) + }; if let Some(first_node_of_new_path) = path.front() { if successors(last_node_of_current_path.target) @@ -503,10 +518,15 @@ fn check_node_reached( } fn check_for_path_obstruction( - mut query: Query<(&Pathfinder, &mut ExecutingPath, &InstanceName)>, + mut query: Query<( + &Pathfinder, + &mut ExecutingPath, + &InstanceName, + &InventoryComponent, + )>, instance_container: Res, ) { - for (pathfinder, mut executing_path, instance_name) in &mut query { + for (pathfinder, mut executing_path, instance_name, inventory) in &mut query { let Some(successors_fn) = pathfinder.successors_fn else { continue; }; @@ -517,7 +537,9 @@ fn check_for_path_obstruction( // obstruction check (the path we're executing isn't possible anymore) let cached_world = CachedWorld::new(world_lock); - let successors = |pos: BlockPos| call_successors_fn(&cached_world, successors_fn, pos); + let mining_cache = MiningCache::new(inventory.inventory_menu.clone()); + let successors = + |pos: BlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos); if let Some(obstructed_index) = check_path_obstructed( executing_path.last_reached_node, @@ -694,6 +716,11 @@ fn stop_pathfinding_on_instance_change( /// permissions, and it'll make them spam *a lot* of commands. /// /// ``` +/// # use azalea::prelude::*; +/// # use azalea::pathfinder::PathfinderDebugParticles; +/// # #[derive(Component, Clone, Default)] +/// # pub struct State; +/// /// async fn handle(mut bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> { /// match event { /// azalea::Event::Init => { @@ -704,6 +731,7 @@ fn stop_pathfinding_on_instance_change( /// } /// _ => {} /// } +/// Ok(()) /// } /// ``` #[derive(Component)] @@ -809,6 +837,7 @@ where pub fn call_successors_fn( cached_world: &CachedWorld, + mining_cache: &MiningCache, successors_fn: SuccessorsFn, pos: BlockPos, ) -> Vec> { @@ -816,6 +845,7 @@ pub fn call_successors_fn( let mut ctx = PathfinderCtx { edges: &mut edges, world: cached_world, + mining_cache, }; successors_fn(&mut ctx, pos); edges diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs index bf1fc5f4..e5b837ea 100644 --- a/azalea/src/pathfinder/moves/mod.rs +++ b/azalea/src/pathfinder/moves/mod.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use crate::{JumpEvent, LookAtEvent}; -use super::{astar, world::CachedWorld}; +use super::{astar, mining::MiningCache, world::CachedWorld}; use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection}; use azalea_core::position::{BlockPos, Vec3}; use bevy_ecs::{entity::Entity, event::EventWriter}; @@ -107,4 +107,5 @@ pub fn default_is_reached( pub struct PathfinderCtx<'a> { pub edges: &'a mut Vec, pub world: &'a CachedWorld, + pub mining_cache: &'a MiningCache, } diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 2b1bfd42..cc077985 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, time::Duration}; -use azalea_client::PhysicsState; +use azalea_client::{inventory::InventoryComponent, PhysicsState}; use azalea_core::{position::Vec3, resource_location::ResourceLocation}; use azalea_entity::{ attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics, @@ -20,6 +20,7 @@ pub struct SimulatedPlayerBundle { pub physics: Physics, pub physics_state: PhysicsState, pub attributes: Attributes, + pub inventory: InventoryComponent, } impl SimulatedPlayerBundle { @@ -37,6 +38,7 @@ impl SimulatedPlayerBundle { speed: AttributeInstance::new(0.1), attack_speed: AttributeInstance::new(4.0), }, + inventory: InventoryComponent::default(), } } }