diff options
Diffstat (limited to 'azalea/src')
| -rw-r--r-- | azalea/src/auto_tool.rs | 84 | ||||
| -rw-r--r-- | azalea/src/bot.rs | 3 | ||||
| -rw-r--r-- | azalea/src/container.rs | 4 | ||||
| -rw-r--r-- | azalea/src/lib.rs | 1 | ||||
| -rw-r--r-- | azalea/src/pathfinder/mining.rs | 30 | ||||
| -rw-r--r-- | azalea/src/pathfinder/mod.rs | 52 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/mod.rs | 3 | ||||
| -rw-r--r-- | azalea/src/pathfinder/simulation.rs | 4 |
8 files changed, 167 insertions, 14 deletions
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::<dyn Block>::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<ContainerHandle> { 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<u32, f32>, + 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<InstanceContainer>, ) { 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<PathFoundEvent>, - mut query: Query<(&mut Pathfinder, Option<&mut ExecutingPath>, &InstanceName)>, + mut query: Query<( + &mut Pathfinder, + Option<&mut ExecutingPath>, + &InstanceName, + &InventoryComponent, + )>, instance_container: Res<InstanceContainer>, 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<InstanceContainer>, ) { - 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<astar::Edge<BlockPos, moves::MoveData>> { @@ -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<Edge>, 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(), } } } |
