aboutsummaryrefslogtreecommitdiff
path: root/azalea/src
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2024-08-15 01:25:11 +0000
committermat <git@matdoes.dev>2024-08-15 01:25:11 +0000
commit73091d8f937192aca4c4bbc740c78d4d188f6ee1 (patch)
tree440e804d3a84583fb1b6b43a47724f5a17d001b7 /azalea/src
parentdec544a52ba738e2d0e2c3a042e5ccc0cb336ffb (diff)
downloadazalea-drasl-73091d8f937192aca4c4bbc740c78d4d188f6ee1.tar.xz
fix sometimes being able to mine blocks through walls
Diffstat (limited to 'azalea/src')
-rw-r--r--azalea/src/pathfinder/extras/utils.rs153
-rw-r--r--azalea/src/pathfinder/mod.rs72
-rw-r--r--azalea/src/pathfinder/simulation.rs15
3 files changed, 209 insertions, 31 deletions
diff --git a/azalea/src/pathfinder/extras/utils.rs b/azalea/src/pathfinder/extras/utils.rs
new file mode 100644
index 00000000..30b1ae52
--- /dev/null
+++ b/azalea/src/pathfinder/extras/utils.rs
@@ -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));
+ }
+}
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index a1bdaaad..becc163d 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -866,12 +866,34 @@ 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 {
chunk_positions.insert(ChunkPos::from(block_pos));
@@ -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),
diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs
index 2803b846..e8ba4dbd 100644
--- a/azalea/src/pathfinder/simulation.rs
+++ b/azalea/src/pathfinder/simulation.rs
@@ -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()
}
}