mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 22:52:32 +00:00
start adding mining to pathfinder
This commit is contained in:
parent
0297b8aace
commit
9a687f0ffe
23 changed files with 221 additions and 51 deletions
|
@ -1,7 +1,7 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{exceptions::CommandSyntaxException, string_reader::StringReader};
|
use crate::{exceptions::CommandSyntaxException, string_reader::StringReader};
|
||||||
|
|
||||||
pub trait ArgumentType {
|
pub trait ArgumentType {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException>;
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
||||||
|
@ -10,8 +10,8 @@ use super::ArgumentType;
|
||||||
struct Boolean;
|
struct Boolean;
|
||||||
|
|
||||||
impl ArgumentType for Boolean {
|
impl ArgumentType for Boolean {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
Ok(Rc::new(reader.read_boolean()?))
|
Ok(Arc::new(reader.read_boolean()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext,
|
context::CommandContext,
|
||||||
|
@ -15,7 +15,7 @@ struct Double {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentType for Double {
|
impl ArgumentType for Double {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_double()?;
|
let result = reader.read_double()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum {
|
||||||
|
@ -38,7 +38,7 @@ impl ArgumentType for Double {
|
||||||
.create_with_context(reader));
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Rc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext,
|
context::CommandContext,
|
||||||
|
@ -15,7 +15,7 @@ struct Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentType for Float {
|
impl ArgumentType for Float {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_float()?;
|
let result = reader.read_float()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum {
|
||||||
|
@ -38,7 +38,7 @@ impl ArgumentType for Float {
|
||||||
.create_with_context(reader));
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Rc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext,
|
context::CommandContext,
|
||||||
|
@ -15,7 +15,7 @@ struct Integer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentType for Integer {
|
impl ArgumentType for Integer {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_int()?;
|
let result = reader.read_int()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum {
|
||||||
|
@ -38,7 +38,7 @@ impl ArgumentType for Integer {
|
||||||
.create_with_context(reader));
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Rc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext,
|
context::CommandContext,
|
||||||
|
@ -15,7 +15,7 @@ struct Long {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentType for Long {
|
impl ArgumentType for Long {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_long()?;
|
let result = reader.read_long()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum {
|
||||||
|
@ -38,7 +38,7 @@ impl ArgumentType for Long {
|
||||||
.create_with_context(reader));
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Rc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
|
||||||
|
@ -17,7 +17,7 @@ pub enum StringArgument {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentType for StringArgument {
|
impl ArgumentType for StringArgument {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let result = match self {
|
let result = match self {
|
||||||
StringArgument::SingleWord => reader.read_unquoted_string().to_string(),
|
StringArgument::SingleWord => reader.read_unquoted_string().to_string(),
|
||||||
StringArgument::QuotablePhrase => reader.read_string()?,
|
StringArgument::QuotablePhrase => reader.read_string()?,
|
||||||
|
@ -27,7 +27,7 @@ impl ArgumentType for StringArgument {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Rc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
||||||
use crate::{
|
use crate::{
|
||||||
arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader,
|
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,
|
/// An argument node type. The `T` type parameter is the type of the argument,
|
||||||
/// which can be anything.
|
/// which can be anything.
|
||||||
|
@ -19,7 +19,7 @@ impl Argument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
|
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
self.parser.parse(reader)
|
self.parser.parse(reader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl<S> CommandContext<S> {
|
||||||
!self.nodes.is_empty()
|
!self.nodes.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn argument(&self, name: &str) -> Option<Rc<dyn Any>> {
|
pub fn argument(&self, name: &str) -> Option<Arc<dyn Any>> {
|
||||||
let argument = self.arguments.get(name);
|
let argument = self.arguments.get(name);
|
||||||
argument.map(|a| a.result.clone())
|
argument.map(|a| a.result.clone())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::string_range::StringRange;
|
use super::string_range::StringRange;
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ParsedArgument {
|
pub struct ParsedArgument {
|
||||||
pub range: StringRange,
|
pub range: StringRange,
|
||||||
pub result: Rc<dyn Any>,
|
pub result: Arc<dyn Any>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component present on all local players that have an inventory.
|
/// A component present on all local players that have an inventory.
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, Clone)]
|
||||||
pub struct InventoryComponent {
|
pub struct InventoryComponent {
|
||||||
/// A component that contains the player's inventory menu. This is
|
/// A component that contains the player's inventory menu. This is
|
||||||
/// guaranteed to be a `Menu::Player`.
|
/// guaranteed to be a `Menu::Player`.
|
||||||
|
|
|
@ -125,6 +125,11 @@ impl From<Vec<u8>> for BitSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of bits with a known fixed size.
|
/// 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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct FixedBitSet<const N: usize>
|
pub struct FixedBitSet<const N: usize>
|
||||||
where
|
where
|
||||||
|
|
|
@ -366,6 +366,12 @@ pub struct LocalEntity;
|
||||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||||
pub struct FluidOnEyes(azalea_registry::Fluid);
|
pub struct FluidOnEyes(azalea_registry::Fluid);
|
||||||
|
|
||||||
|
impl FluidOnEyes {
|
||||||
|
pub fn new(fluid: azalea_registry::Fluid) -> Self {
|
||||||
|
Self(fluid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod tests {
|
// mod tests {
|
||||||
// use super::*;
|
// use super::*;
|
||||||
|
|
|
@ -4,6 +4,14 @@ use azalea_registry as registry;
|
||||||
|
|
||||||
use crate::{effects, enchantments, FluidOnEyes, Physics};
|
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(
|
pub fn get_mine_progress(
|
||||||
block: &dyn Block,
|
block: &dyn Block,
|
||||||
held_item: registry::Item,
|
held_item: registry::Item,
|
||||||
|
@ -11,16 +19,6 @@ pub fn get_mine_progress(
|
||||||
fluid_on_eyes: &FluidOnEyes,
|
fluid_on_eyes: &FluidOnEyes,
|
||||||
physics: &Physics,
|
physics: &Physics,
|
||||||
) -> f32 {
|
) -> 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 block_behavior: BlockBehavior = block.behavior();
|
||||||
|
|
||||||
let destroy_time = block_behavior.destroy_time;
|
let destroy_time = block_behavior.destroy_time;
|
||||||
|
|
|
@ -4,12 +4,14 @@ use azalea::{
|
||||||
pathfinder::{
|
pathfinder::{
|
||||||
astar::{self, a_star},
|
astar::{self, a_star},
|
||||||
goals::BlockPosGoal,
|
goals::BlockPosGoal,
|
||||||
|
mining::MiningCache,
|
||||||
world::CachedWorld,
|
world::CachedWorld,
|
||||||
Goal,
|
Goal,
|
||||||
},
|
},
|
||||||
BlockPos,
|
BlockPos,
|
||||||
};
|
};
|
||||||
use azalea_core::position::{ChunkBlockPos, ChunkPos};
|
use azalea_core::position::{ChunkBlockPos, ChunkPos};
|
||||||
|
use azalea_inventory::Menu;
|
||||||
use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage};
|
use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage};
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -80,10 +82,16 @@ fn bench_pathfinder(c: &mut Criterion) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let (world, start, end) = generate_bedrock_world(&mut partial_chunks, 4);
|
let (world, start, end) = generate_bedrock_world(&mut partial_chunks, 4);
|
||||||
let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.into())));
|
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 goal = BlockPosGoal(end);
|
||||||
|
|
||||||
let successors = |pos: BlockPos| {
|
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(
|
let astar::Path { movements, partial } = a_star(
|
||||||
|
|
84
azalea/src/auto_tool.rs
Normal file
84
azalea/src/auto_tool.rs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)]
|
#[derive(Default, Component)]
|
||||||
pub struct Bot {
|
pub struct Bot {
|
||||||
jumping_once: bool,
|
jumping_once: bool,
|
||||||
|
|
|
@ -79,6 +79,10 @@ impl ContainerClientExt for Client {
|
||||||
/// Note that this will send a packet to the server once it's dropped. Also,
|
/// 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
|
/// due to how it's implemented, you could call this function multiple times
|
||||||
/// while another inventory handle already exists (but you shouldn't).
|
/// 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> {
|
fn open_inventory(&mut self) -> Option<ContainerHandle> {
|
||||||
let ecs = self.ecs.lock();
|
let ecs = self.ecs.lock();
|
||||||
let inventory = ecs
|
let inventory = ecs
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
pub mod accept_resource_packs;
|
pub mod accept_resource_packs;
|
||||||
mod auto_respawn;
|
mod auto_respawn;
|
||||||
|
pub mod auto_tool;
|
||||||
mod bot;
|
mod bot;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod nearest_entity;
|
pub mod nearest_entity;
|
||||||
|
|
30
azalea/src/pathfinder/mining.rs
Normal file
30
azalea/src/pathfinder/mining.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
pub mod astar;
|
pub mod astar;
|
||||||
pub mod costs;
|
pub mod costs;
|
||||||
pub mod goals;
|
pub mod goals;
|
||||||
|
pub mod mining;
|
||||||
pub mod moves;
|
pub mod moves;
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
@ -23,6 +24,7 @@ use crate::ecs::{
|
||||||
use crate::pathfinder::moves::PathfinderCtx;
|
use crate::pathfinder::moves::PathfinderCtx;
|
||||||
use crate::pathfinder::world::CachedWorld;
|
use crate::pathfinder::world::CachedWorld;
|
||||||
use azalea_client::chat::SendChatEvent;
|
use azalea_client::chat::SendChatEvent;
|
||||||
|
use azalea_client::inventory::{InventoryComponent, InventorySet};
|
||||||
use azalea_client::movement::walk_listener;
|
use azalea_client::movement::walk_listener;
|
||||||
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||||
use azalea_core::position::{BlockPos, Vec3};
|
use azalea_core::position::{BlockPos, Vec3};
|
||||||
|
@ -45,6 +47,7 @@ use std::sync::atomic::{self, AtomicUsize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use self::mining::MiningCache;
|
||||||
use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn};
|
use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
@ -82,7 +85,8 @@ impl Plugin for PathfinderPlugin {
|
||||||
handle_stop_pathfinding_event,
|
handle_stop_pathfinding_event,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.before(walk_listener),
|
.before(walk_listener)
|
||||||
|
.before(InventorySet),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +120,7 @@ pub struct GotoEvent {
|
||||||
/// `pathfinder::moves::default_move`
|
/// `pathfinder::moves::default_move`
|
||||||
pub successors_fn: SuccessorsFn,
|
pub successors_fn: SuccessorsFn,
|
||||||
}
|
}
|
||||||
#[derive(Event)]
|
#[derive(Event, Clone)]
|
||||||
pub struct PathFoundEvent {
|
pub struct PathFoundEvent {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub start: BlockPos,
|
pub start: BlockPos,
|
||||||
|
@ -175,13 +179,14 @@ fn goto_listener(
|
||||||
Option<&ExecutingPath>,
|
Option<&ExecutingPath>,
|
||||||
&Position,
|
&Position,
|
||||||
&InstanceName,
|
&InstanceName,
|
||||||
|
&InventoryComponent,
|
||||||
)>,
|
)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
let thread_pool = AsyncComputeTaskPool::get();
|
let thread_pool = AsyncComputeTaskPool::get();
|
||||||
|
|
||||||
for event in events.iter() {
|
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)
|
.get_mut(event.entity)
|
||||||
.expect("Called goto on an entity that's not in the world");
|
.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_atomic = pathfinder.goto_id.clone();
|
||||||
let goto_id = goto_id_atomic.fetch_add(1, atomic::Ordering::Relaxed) + 1;
|
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 {
|
let task = thread_pool.spawn(async move {
|
||||||
debug!("start: {start:?}");
|
debug!("start: {start:?}");
|
||||||
|
|
||||||
let cached_world = CachedWorld::new(world_lock);
|
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;
|
let mut attempt_number = 0;
|
||||||
|
|
||||||
|
@ -311,12 +319,17 @@ fn handle_tasks(
|
||||||
// set the path for the target entity when we get the PathFoundEvent
|
// set the path for the target entity when we get the PathFoundEvent
|
||||||
fn path_found_listener(
|
fn path_found_listener(
|
||||||
mut events: EventReader<PathFoundEvent>,
|
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>,
|
instance_container: Res<InstanceContainer>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
for event in events.iter() {
|
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)
|
.get_mut(event.entity)
|
||||||
.expect("Path found for an entity that doesn't have a pathfinder");
|
.expect("Path found for an entity that doesn't have a pathfinder");
|
||||||
if let Some(path) = &event.path {
|
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");
|
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
||||||
let successors_fn: moves::SuccessorsFn = event.successors_fn;
|
let successors_fn: moves::SuccessorsFn = event.successors_fn;
|
||||||
let cached_world = CachedWorld::new(world_lock);
|
let cached_world = CachedWorld::new(world_lock);
|
||||||
let successors =
|
let mining_cache = MiningCache::new(inventory.inventory_menu.clone());
|
||||||
|pos: BlockPos| call_successors_fn(&cached_world, successors_fn, pos);
|
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 let Some(first_node_of_new_path) = path.front() {
|
||||||
if successors(last_node_of_current_path.target)
|
if successors(last_node_of_current_path.target)
|
||||||
|
@ -503,10 +518,15 @@ fn check_node_reached(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_path_obstruction(
|
fn check_for_path_obstruction(
|
||||||
mut query: Query<(&Pathfinder, &mut ExecutingPath, &InstanceName)>,
|
mut query: Query<(
|
||||||
|
&Pathfinder,
|
||||||
|
&mut ExecutingPath,
|
||||||
|
&InstanceName,
|
||||||
|
&InventoryComponent,
|
||||||
|
)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
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 {
|
let Some(successors_fn) = pathfinder.successors_fn else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -517,7 +537,9 @@ fn check_for_path_obstruction(
|
||||||
|
|
||||||
// obstruction check (the path we're executing isn't possible anymore)
|
// obstruction check (the path we're executing isn't possible anymore)
|
||||||
let cached_world = CachedWorld::new(world_lock);
|
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(
|
if let Some(obstructed_index) = check_path_obstructed(
|
||||||
executing_path.last_reached_node,
|
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.
|
/// 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<()> {
|
/// async fn handle(mut bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> {
|
||||||
/// match event {
|
/// match event {
|
||||||
/// azalea::Event::Init => {
|
/// azalea::Event::Init => {
|
||||||
|
@ -704,6 +731,7 @@ fn stop_pathfinding_on_instance_change(
|
||||||
/// }
|
/// }
|
||||||
/// _ => {}
|
/// _ => {}
|
||||||
/// }
|
/// }
|
||||||
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
@ -809,6 +837,7 @@ where
|
||||||
|
|
||||||
pub fn call_successors_fn(
|
pub fn call_successors_fn(
|
||||||
cached_world: &CachedWorld,
|
cached_world: &CachedWorld,
|
||||||
|
mining_cache: &MiningCache,
|
||||||
successors_fn: SuccessorsFn,
|
successors_fn: SuccessorsFn,
|
||||||
pos: BlockPos,
|
pos: BlockPos,
|
||||||
) -> Vec<astar::Edge<BlockPos, moves::MoveData>> {
|
) -> Vec<astar::Edge<BlockPos, moves::MoveData>> {
|
||||||
|
@ -816,6 +845,7 @@ pub fn call_successors_fn(
|
||||||
let mut ctx = PathfinderCtx {
|
let mut ctx = PathfinderCtx {
|
||||||
edges: &mut edges,
|
edges: &mut edges,
|
||||||
world: cached_world,
|
world: cached_world,
|
||||||
|
mining_cache,
|
||||||
};
|
};
|
||||||
successors_fn(&mut ctx, pos);
|
successors_fn(&mut ctx, pos);
|
||||||
edges
|
edges
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{JumpEvent, LookAtEvent};
|
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_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection};
|
||||||
use azalea_core::position::{BlockPos, Vec3};
|
use azalea_core::position::{BlockPos, Vec3};
|
||||||
use bevy_ecs::{entity::Entity, event::EventWriter};
|
use bevy_ecs::{entity::Entity, event::EventWriter};
|
||||||
|
@ -107,4 +107,5 @@ pub fn default_is_reached(
|
||||||
pub struct PathfinderCtx<'a> {
|
pub struct PathfinderCtx<'a> {
|
||||||
pub edges: &'a mut Vec<Edge>,
|
pub edges: &'a mut Vec<Edge>,
|
||||||
pub world: &'a CachedWorld,
|
pub world: &'a CachedWorld,
|
||||||
|
pub mining_cache: &'a MiningCache,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::{sync::Arc, time::Duration};
|
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_core::{position::Vec3, resource_location::ResourceLocation};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics,
|
attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics,
|
||||||
|
@ -20,6 +20,7 @@ pub struct SimulatedPlayerBundle {
|
||||||
pub physics: Physics,
|
pub physics: Physics,
|
||||||
pub physics_state: PhysicsState,
|
pub physics_state: PhysicsState,
|
||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
|
pub inventory: InventoryComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimulatedPlayerBundle {
|
impl SimulatedPlayerBundle {
|
||||||
|
@ -37,6 +38,7 @@ impl SimulatedPlayerBundle {
|
||||||
speed: AttributeInstance::new(0.1),
|
speed: AttributeInstance::new(0.1),
|
||||||
attack_speed: AttributeInstance::new(4.0),
|
attack_speed: AttributeInstance::new(4.0),
|
||||||
},
|
},
|
||||||
|
inventory: InventoryComponent::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue