mirror of
https://github.com/mat-1/azalea.git
synced 2024-11-08 14:38:44 +00:00
fix sometimes being able to mine blocks through walls
This commit is contained in:
parent
dec544a52b
commit
73091d8f93
6 changed files with 232 additions and 46 deletions
|
@ -396,16 +396,16 @@ impl MineProgress {
|
|||
/// A component that stores the number of ticks that we've been mining the same
|
||||
/// block for. This is a float even though it should only ever be a round
|
||||
/// number.
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct MineTicks(pub f32);
|
||||
|
||||
/// A component that stores the position of the block we're currently mining.
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct MineBlockPos(pub Option<BlockPos>);
|
||||
|
||||
/// A component that contains the item we're currently using to mine. If we're
|
||||
/// not mining anything, it'll be [`ItemSlot::Empty`].
|
||||
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct MineItem(pub ItemSlot);
|
||||
|
||||
/// Sent when we completed mining a block.
|
||||
|
|
|
@ -62,6 +62,12 @@ pub fn ceil_log2(x: u32) -> u32 {
|
|||
u32::BITS - x.leading_zeros()
|
||||
}
|
||||
|
||||
pub fn fract(x: f64) -> f64 {
|
||||
let x_int = x as i64 as f64;
|
||||
let floor = if x < x_int { x_int - 1. } else { x_int };
|
||||
x - floor
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -2,7 +2,7 @@ use azalea_block::BlockState;
|
|||
use azalea_core::{
|
||||
block_hit_result::BlockHitResult,
|
||||
direction::Direction,
|
||||
math::{lerp, EPSILON},
|
||||
math::{self, lerp, EPSILON},
|
||||
position::{BlockPos, Vec3},
|
||||
};
|
||||
use azalea_inventory::ItemSlot;
|
||||
|
@ -111,13 +111,12 @@ fn clip_with_interaction_override(
|
|||
block_state: &BlockState,
|
||||
) -> Option<BlockHitResult> {
|
||||
let block_hit_result = block_shape.clip(from, to, block_pos);
|
||||
println!("block_hit_result: {block_hit_result:?}");
|
||||
if let Some(block_hit_result) = block_hit_result {
|
||||
// TODO: minecraft calls .getInteractionShape here
|
||||
// are there even any blocks that have a physics shape different from the
|
||||
// interaction shape???
|
||||
// (if not then you can delete this comment)
|
||||
// (if there are then you have to implement BlockState::interaction_shape, lol
|
||||
// have fun)
|
||||
// some blocks (like tall grass) have a physics shape that's different from the
|
||||
// interaction shape, so we need to implement BlockState::interaction_shape. lol
|
||||
// have fun
|
||||
let interaction_shape = block_state.shape();
|
||||
let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
|
||||
if let Some(interaction_hit_result) = interaction_hit_result {
|
||||
|
@ -191,24 +190,27 @@ pub fn traverse_blocks<C, T>(
|
|||
let mut percentage = Vec3 {
|
||||
x: percentage_step.x
|
||||
* if vec_sign.x > 0. {
|
||||
1. - right_before_start.x.fract()
|
||||
1. - math::fract(right_before_start.x)
|
||||
} else {
|
||||
right_before_start.x.fract().abs()
|
||||
math::fract(right_before_start.x)
|
||||
},
|
||||
y: percentage_step.y
|
||||
* if vec_sign.y > 0. {
|
||||
1. - right_before_start.y.fract()
|
||||
1. - math::fract(right_before_start.y)
|
||||
} else {
|
||||
right_before_start.y.fract().abs()
|
||||
math::fract(right_before_start.y)
|
||||
},
|
||||
z: percentage_step.z
|
||||
* if vec_sign.z > 0. {
|
||||
1. - right_before_start.z.fract()
|
||||
1. - math::fract(right_before_start.z)
|
||||
} else {
|
||||
right_before_start.z.fract().abs()
|
||||
math::fract(right_before_start.z)
|
||||
},
|
||||
};
|
||||
|
||||
println!("percentage_step: {percentage_step:?}");
|
||||
println!("percentage: {percentage:?}");
|
||||
|
||||
loop {
|
||||
if percentage.x > 1. && percentage.y > 1. && percentage.z > 1. {
|
||||
return get_miss_result(&context);
|
||||
|
|
153
azalea/src/pathfinder/extras/utils.rs
Normal file
153
azalea/src/pathfinder/extras/utils.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
//! Random utility functions that are useful for bots.
|
||||
|
||||
use azalea_core::position::{BlockPos, Vec3};
|
||||
use azalea_entity::direction_looking_at;
|
||||
use azalea_world::ChunkStorage;
|
||||
|
||||
/// Get a vec of block positions that we can reach from this position.
|
||||
pub fn get_reachable_blocks_around_player(
|
||||
player_position: BlockPos,
|
||||
chunk_storage: &ChunkStorage,
|
||||
) -> Vec<BlockPos> {
|
||||
// check a 12x12x12 area around the player
|
||||
let mut blocks = Vec::new();
|
||||
|
||||
for x in -6..=6 {
|
||||
// y is 1 up to somewhat offset for the eye height
|
||||
for y in -5..=7 {
|
||||
for z in -6..=6 {
|
||||
let block_pos = player_position + BlockPos::new(x, y, z);
|
||||
let block_state = chunk_storage
|
||||
.get_block_state(&block_pos)
|
||||
.unwrap_or_default();
|
||||
|
||||
if block_state.is_air() {
|
||||
// fast path, skip if it's air
|
||||
continue;
|
||||
}
|
||||
|
||||
if can_reach_block(chunk_storage, player_position, block_pos) {
|
||||
blocks.push(block_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks
|
||||
}
|
||||
|
||||
pub fn pick_closest_block(position: BlockPos, blocks: &[BlockPos]) -> Option<BlockPos> {
|
||||
// pick the closest one and mine it
|
||||
let mut closest_block_pos = None;
|
||||
let mut closest_distance = i32::MAX;
|
||||
for block_pos in &blocks[1..] {
|
||||
if block_pos.y < position.y {
|
||||
// skip blocks below us at first
|
||||
continue;
|
||||
}
|
||||
let distance = block_pos.distance_squared_to(&position);
|
||||
if distance < closest_distance {
|
||||
closest_block_pos = Some(*block_pos);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if closest_block_pos.is_none() {
|
||||
// ok now check every block if the only ones around us are below
|
||||
for block_pos in blocks {
|
||||
let distance = block_pos.distance_squared_to(&position);
|
||||
if distance < closest_distance {
|
||||
closest_block_pos = Some(*block_pos);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest_block_pos
|
||||
}
|
||||
|
||||
/// Return the block that we'd be looking at if we were at a given position and
|
||||
/// looking at a given block.
|
||||
///
|
||||
/// This is useful for telling if we'd be able to reach a block from a certain
|
||||
/// position, like for the pathfinder's [`ReachBlockPosGoal`].
|
||||
///
|
||||
/// Also see [`get_hit_result_while_looking_at_with_eye_position`].
|
||||
///
|
||||
/// [`ReachBlockPosGoal`]: crate::pathfinder::goals::ReachBlockPosGoal
|
||||
pub fn get_hit_result_while_looking_at(
|
||||
chunk_storage: &ChunkStorage,
|
||||
player_position: BlockPos,
|
||||
look_target: BlockPos,
|
||||
) -> BlockPos {
|
||||
let eye_position = Vec3 {
|
||||
x: player_position.x as f64 + 0.5,
|
||||
y: player_position.y as f64 + 1.53,
|
||||
z: player_position.z as f64 + 0.5,
|
||||
};
|
||||
get_hit_result_while_looking_at_with_eye_position(chunk_storage, eye_position, look_target)
|
||||
}
|
||||
|
||||
pub fn can_reach_block(
|
||||
chunk_storage: &ChunkStorage,
|
||||
player_position: BlockPos,
|
||||
look_target: BlockPos,
|
||||
) -> bool {
|
||||
let hit_result = get_hit_result_while_looking_at(chunk_storage, player_position, look_target);
|
||||
hit_result == look_target
|
||||
}
|
||||
|
||||
/// Return the block that we'd be looking at if our eyes are at a given position
|
||||
/// and looking at a given block.
|
||||
///
|
||||
/// This is called by [`get_hit_result_while_looking_at`].
|
||||
pub fn get_hit_result_while_looking_at_with_eye_position(
|
||||
chunk_storage: &azalea_world::ChunkStorage,
|
||||
eye_position: Vec3,
|
||||
look_target: BlockPos,
|
||||
) -> BlockPos {
|
||||
let look_direction = direction_looking_at(&eye_position, &look_target.center());
|
||||
let block_hit_result =
|
||||
azalea_client::interact::pick(&look_direction, &eye_position, chunk_storage, 4.5);
|
||||
block_hit_result.block_pos
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use azalea_core::position::ChunkPos;
|
||||
use azalea_world::{Chunk, PartialInstance};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cannot_reach_block_through_wall_when_y_is_negative() {
|
||||
let mut partial_world = PartialInstance::default();
|
||||
let mut world = ChunkStorage::default();
|
||||
partial_world
|
||||
.chunks
|
||||
.set(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()), &mut world);
|
||||
|
||||
let set_solid_block_at = |x, y, z| {
|
||||
partial_world.chunks.set_block_state(
|
||||
&BlockPos::new(x, y, z),
|
||||
azalea_registry::Block::Stone.into(),
|
||||
&world,
|
||||
);
|
||||
};
|
||||
|
||||
let y_offset = -8;
|
||||
|
||||
// walls
|
||||
set_solid_block_at(1, y_offset, 0);
|
||||
set_solid_block_at(1, y_offset + 1, 0);
|
||||
set_solid_block_at(0, y_offset, 1);
|
||||
set_solid_block_at(0, y_offset + 1, 1);
|
||||
// target
|
||||
set_solid_block_at(1, y_offset, 1);
|
||||
|
||||
let player_position = BlockPos::new(0, y_offset, 0);
|
||||
let look_target = BlockPos::new(1, y_offset, 1);
|
||||
|
||||
assert!(!can_reach_block(&world, player_position, look_target));
|
||||
}
|
||||
}
|
|
@ -866,11 +866,33 @@ mod tests {
|
|||
GotoEvent,
|
||||
};
|
||||
|
||||
fn setup_simulation(
|
||||
fn setup_blockposgoal_simulation(
|
||||
partial_chunks: &mut PartialChunkStorage,
|
||||
start_pos: BlockPos,
|
||||
end_pos: BlockPos,
|
||||
solid_blocks: Vec<BlockPos>,
|
||||
) -> Simulation {
|
||||
let mut simulation = setup_simulation_world(partial_chunks, start_pos, solid_blocks);
|
||||
|
||||
// you can uncomment this while debugging tests to get trace logs
|
||||
// simulation.app.add_plugins(bevy_log::LogPlugin {
|
||||
// level: bevy_log::Level::TRACE,
|
||||
// filter: "".to_string(),
|
||||
// });
|
||||
|
||||
simulation.app.world.send_event(GotoEvent {
|
||||
entity: simulation.entity,
|
||||
goal: Arc::new(BlockPosGoal(end_pos)),
|
||||
successors_fn: moves::default_move,
|
||||
allow_mining: false,
|
||||
});
|
||||
simulation
|
||||
}
|
||||
|
||||
fn setup_simulation_world(
|
||||
partial_chunks: &mut PartialChunkStorage,
|
||||
start_pos: BlockPos,
|
||||
solid_blocks: Vec<BlockPos>,
|
||||
) -> Simulation {
|
||||
let mut chunk_positions = HashSet::new();
|
||||
for block_pos in &solid_blocks {
|
||||
|
@ -889,43 +911,33 @@ mod tests {
|
|||
start_pos.y as f64,
|
||||
start_pos.z as f64 + 0.5,
|
||||
));
|
||||
let mut simulation = Simulation::new(chunks, player);
|
||||
|
||||
// you can uncomment this while debugging tests to get trace logs
|
||||
// simulation.app.add_plugins(bevy_log::LogPlugin {
|
||||
// level: bevy_log::Level::TRACE,
|
||||
// filter: "".to_string(),
|
||||
// });
|
||||
|
||||
simulation.app.world.send_event(GotoEvent {
|
||||
entity: simulation.entity,
|
||||
goal: Arc::new(BlockPosGoal(end_pos)),
|
||||
successors_fn: moves::default_move,
|
||||
allow_mining: false,
|
||||
});
|
||||
simulation
|
||||
Simulation::new(chunks, player)
|
||||
}
|
||||
|
||||
pub fn assert_simulation_reaches(simulation: &mut Simulation, ticks: usize, end_pos: BlockPos) {
|
||||
// wait until the bot starts moving
|
||||
wait_until_bot_starts_moving(simulation);
|
||||
for _ in 0..ticks {
|
||||
simulation.tick();
|
||||
}
|
||||
assert_eq!(BlockPos::from(simulation.position()), end_pos);
|
||||
}
|
||||
|
||||
pub fn wait_until_bot_starts_moving(simulation: &mut Simulation) {
|
||||
let start_pos = simulation.position();
|
||||
let start_time = Instant::now();
|
||||
while simulation.position() == start_pos
|
||||
&& !simulation.is_mining()
|
||||
&& start_time.elapsed() < Duration::from_millis(500)
|
||||
{
|
||||
simulation.tick();
|
||||
std::thread::yield_now();
|
||||
}
|
||||
for _ in 0..ticks {
|
||||
simulation.tick();
|
||||
}
|
||||
assert_eq!(BlockPos::from(simulation.position()), end_pos,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_forward() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(0, 71, 1),
|
||||
|
@ -937,7 +949,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_double_diagonal_with_walls() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(2, 71, 2),
|
||||
|
@ -955,7 +967,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_jump_with_sideways_momentum() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 3),
|
||||
BlockPos::new(5, 76, 0),
|
||||
|
@ -977,7 +989,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parkour_2_block_gap() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(0, 71, 3),
|
||||
|
@ -989,7 +1001,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_descend_and_parkour_2_block_gap() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(3, 67, 4),
|
||||
|
@ -1008,7 +1020,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_small_descend_and_parkour_2_block_gap() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(0, 70, 5),
|
||||
|
@ -1025,7 +1037,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_quickly_descend() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(0, 68, 3),
|
||||
|
@ -1042,7 +1054,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_2_gap_ascend_thrice() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(3, 74, 0),
|
||||
|
@ -1059,7 +1071,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_consecutive_3_gap_parkour() {
|
||||
let mut partial_chunks = PartialChunkStorage::default();
|
||||
let mut simulation = setup_simulation(
|
||||
let mut simulation = setup_blockposgoal_simulation(
|
||||
&mut partial_chunks,
|
||||
BlockPos::new(0, 71, 0),
|
||||
BlockPos::new(4, 71, 12),
|
||||
|
|
|
@ -144,8 +144,21 @@ impl Simulation {
|
|||
self.app.update();
|
||||
self.app.world.run_schedule(GameTick);
|
||||
}
|
||||
pub fn component<T: Component + Clone>(&self) -> T {
|
||||
self.app.world.get::<T>(self.entity).unwrap().clone()
|
||||
}
|
||||
pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
|
||||
self.app.world.get::<T>(self.entity).cloned()
|
||||
}
|
||||
pub fn position(&self) -> Vec3 {
|
||||
**self.app.world.get::<Position>(self.entity).unwrap()
|
||||
*self.component::<Position>()
|
||||
}
|
||||
pub fn is_mining(&self) -> bool {
|
||||
// return true if the component is present and Some
|
||||
self.get_component::<azalea_client::mining::MineBlockPos>()
|
||||
.map(|c| *c)
|
||||
.flatten()
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue