From f03e0c22355778a9863cccb5a59d852278d60701 Mon Sep 17 00:00:00 2001 From: mat Date: Tue, 24 Dec 2024 08:48:36 +0000 Subject: [PATCH] fix parsing Dust particle and treat waterlogged blocks as liquid in pathfinder --- azalea-block/azalea-block-macros/src/lib.rs | 7 +-- azalea-block/src/lib.rs | 6 +- azalea-core/src/bitset.rs | 4 +- azalea-core/src/color.rs | 55 +++++++++++++++++++ azalea-core/src/lib.rs | 1 + azalea-core/src/position.rs | 14 +++++ azalea-entity/src/particle.rs | 27 ++------- .../src/packets/game/c_level_particles.rs | 11 ++-- azalea/Cargo.toml | 4 ++ azalea/benches/checks.rs | 36 ++++++++++++ azalea/benches/pathfinder.rs | 4 +- azalea/examples/testbot/commands/debug.rs | 15 +++++ azalea/examples/testbot/main.rs | 30 +++++++--- azalea/src/pathfinder/mining.rs | 11 +++- azalea/src/pathfinder/mod.rs | 12 ++-- 15 files changed, 183 insertions(+), 54 deletions(-) create mode 100644 azalea-core/src/color.rs create mode 100644 azalea/benches/checks.rs diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 742d1fc9..de13ff6e 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -694,11 +694,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let last_state_id = state_id - 1; let mut generated = quote! { impl BlockState { - /// Returns the highest possible state ID. - #[inline] - pub fn max_state() -> u32 { - #last_state_id - } + /// The highest possible block state ID. + pub const MAX_STATE: u32 = #last_state_id; /// Get a property from this block state. Will be `None` if the block can't have the property. /// diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index c1655919..3de0aa5a 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -56,7 +56,7 @@ impl BlockState { #[inline] pub fn is_valid_state(state_id: u32) -> bool { - state_id <= Self::max_state() + state_id <= Self::MAX_STATE } /// Returns true if the block is air. This only checks for normal air, not @@ -184,8 +184,8 @@ mod tests { fn test_from_u32() { assert_eq!(BlockState::try_from(0).unwrap(), BlockState::AIR); - assert!(BlockState::try_from(BlockState::max_state()).is_ok()); - assert!(BlockState::try_from(BlockState::max_state() + 1).is_err()); + assert!(BlockState::try_from(BlockState::MAX_STATE).is_ok()); + assert!(BlockState::try_from(BlockState::MAX_STATE + 1).is_err()); } #[test] diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs index 76b04885..5af05e19 100755 --- a/azalea-core/src/bitset.rs +++ b/azalea-core/src/bitset.rs @@ -12,9 +12,9 @@ const ADDRESS_BITS_PER_WORD: usize = 6; // the Index trait requires us to return a reference, but we can't do that impl BitSet { - pub fn new(size: usize) -> Self { + pub fn new(num_bits: usize) -> Self { BitSet { - data: vec![0; size.div_ceil(64)], + data: vec![0; num_bits.div_ceil(64)], } } diff --git a/azalea-core/src/color.rs b/azalea-core/src/color.rs new file mode 100644 index 00000000..eddf5035 --- /dev/null +++ b/azalea-core/src/color.rs @@ -0,0 +1,55 @@ +use azalea_buf::AzBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AzBuf)] +pub struct RgbColor { + value: u32, +} + +impl RgbColor { + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { + value: (r as u32) << 16 | (g as u32) << 8 | b as u32, + } + } + + pub fn red(&self) -> u8 { + (self.value >> 16) as u8 + } + + pub fn green(&self) -> u8 { + (self.value >> 8) as u8 + } + + pub fn blue(&self) -> u8 { + self.value as u8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AzBuf)] +pub struct ArgbColor { + value: u32, +} + +impl ArgbColor { + pub fn new(a: u8, r: u8, g: u8, b: u8) -> Self { + Self { + value: (a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | b as u32, + } + } + + pub fn alpha(&self) -> u8 { + (self.value >> 24) as u8 + } + + pub fn red(&self) -> u8 { + (self.value >> 16) as u8 + } + + pub fn green(&self) -> u8 { + (self.value >> 8) as u8 + } + + pub fn blue(&self) -> u8 { + self.value as u8 + } +} diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index acfb560e..04422146 100755 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -5,6 +5,7 @@ pub mod aabb; pub mod bitset; pub mod block_hit_result; +pub mod color; pub mod cursor3d; pub mod delta; pub mod difficulty; diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index be06e825..ab73c0d1 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -71,6 +71,7 @@ macro_rules! vec3_impl { /// Return a new instance of this position with the z coordinate subtracted /// by the given number. + #[inline] pub fn north(&self, z: $type) -> Self { Self { x: self.x, @@ -80,6 +81,7 @@ macro_rules! vec3_impl { } /// Return a new instance of this position with the x coordinate increased /// by the given number. + #[inline] pub fn east(&self, x: $type) -> Self { Self { x: self.x + x, @@ -89,6 +91,7 @@ macro_rules! vec3_impl { } /// Return a new instance of this position with the z coordinate increased /// by the given number. + #[inline] pub fn south(&self, z: $type) -> Self { Self { x: self.x, @@ -98,6 +101,7 @@ macro_rules! vec3_impl { } /// Return a new instance of this position with the x coordinate subtracted /// by the given number. + #[inline] pub fn west(&self, x: $type) -> Self { Self { x: self.x - x, @@ -110,6 +114,16 @@ macro_rules! vec3_impl { pub fn dot(&self, other: Self) -> $type { self.x * other.x + self.y * other.y + self.z * other.z } + + /// Replace the Y with 0. + #[inline] + pub fn xz(&self) -> Self { + Self { + x: self.x, + y: <$type>::default(), + z: self.z, + } + } } impl Add for &$name { diff --git a/azalea-entity/src/particle.rs b/azalea-entity/src/particle.rs index 76559e58..a8710eff 100755 --- a/azalea-entity/src/particle.rs +++ b/azalea-entity/src/particle.rs @@ -1,5 +1,6 @@ +use azalea_block::BlockState; use azalea_buf::AzBuf; -use azalea_core::position::BlockPos; +use azalea_core::{color::RgbColor, position::BlockPos}; use azalea_inventory::ItemStack; use azalea_registry::ParticleKind; use bevy_ecs::component::Component; @@ -251,37 +252,21 @@ impl From for Particle { #[derive(Debug, Clone, AzBuf, Default)] pub struct BlockParticle { - #[var] - pub block_state: i32, + pub block_state: BlockState, } #[derive(Debug, Clone, AzBuf, Default)] pub struct DustParticle { - /// Red value, 0-1 - pub red: f32, - /// Green value, 0-1 - pub green: f32, - /// Blue value, 0-1 - pub blue: f32, + pub color: RgbColor, /// The scale, will be clamped between 0.01 and 4. pub scale: f32, } #[derive(Debug, Clone, AzBuf, Default)] pub struct DustColorTransitionParticle { - /// Red value, 0-1 - pub from_red: f32, - /// Green value, 0-1 - pub from_green: f32, - /// Blue value, 0-1 - pub from_blue: f32, + pub from: RgbColor, + pub to: RgbColor, /// The scale, will be clamped between 0.01 and 4. pub scale: f32, - /// Red value, 0-1 - pub to_red: f32, - /// Green value, 0-1 - pub to_green: f32, - /// Blue value, 0-1 - pub to_blue: f32, } #[derive(Debug, Clone, AzBuf, Default)] diff --git a/azalea-protocol/src/packets/game/c_level_particles.rs b/azalea-protocol/src/packets/game/c_level_particles.rs index 4f77af84..d54315ab 100755 --- a/azalea-protocol/src/packets/game/c_level_particles.rs +++ b/azalea-protocol/src/packets/game/c_level_particles.rs @@ -26,12 +26,11 @@ mod tests { #[test] fn test_c_level_particles_packet() { - let slice = &[ - 0, 0, 64, 36, 19, 1, 192, 139, 224, 69, 64, 91, 192, 0, 0, 0, 0, 0, 63, 229, 66, 62, - 20, 132, 232, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 25, 153, 154, 0, 0, 0, 70, - 1, 9, - ][..]; - let mut bytes = Cursor::new(slice); + #[rustfmt::skip] + let slice = [ + 0, 0, 64, 156, 51, 153, 153, 153, 153, 154, 192, 64, 140, 204, 204, 204, 204, 205, 63, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 13, 255, 0, 255, 255, 63, 128, 0, 0 + ]; + let mut bytes = Cursor::new(slice.as_slice()); let packet = ClientboundLevelParticles::azalea_read(&mut bytes).unwrap(); println!("{packet:?}"); diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index ab11ccb3..59f253bd 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -64,3 +64,7 @@ harness = false [[bench]] name = "physics" harness = false + +[[bench]] +name = "checks" +harness = false diff --git a/azalea/benches/checks.rs b/azalea/benches/checks.rs new file mode 100644 index 00000000..b246f1f6 --- /dev/null +++ b/azalea/benches/checks.rs @@ -0,0 +1,36 @@ +use std::hint::black_box; + +use azalea::pathfinder::mining::MiningCache; +pub use azalea_registry as registry; +use criterion::{criterion_group, criterion_main, Criterion}; + +fn benchmark(c: &mut Criterion) { + let mining_cache = MiningCache::new(None); + + let stone = registry::Block::Stone.into(); + c.bench_function("is_liquid stone", |b| { + b.iter(|| mining_cache.is_liquid(black_box(stone))); + }); + + let water = registry::Block::Water.into(); + c.bench_function("is_liquid water", |b| { + b.iter(|| mining_cache.is_liquid(black_box(water))); + }); + + let lava = registry::Block::Lava.into(); + c.bench_function("is_liquid lava", |b| { + b.iter(|| mining_cache.is_liquid(black_box(lava))); + }); + + let waterlogged_slab = azalea_block::blocks::OakSlab { + kind: azalea_block::properties::Type::Bottom, + waterlogged: true, + } + .into(); + c.bench_function("is_liquid waterlogged slab", |b| { + b.iter(|| mining_cache.is_liquid(black_box(waterlogged_slab))); + }); +} + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index 4e42c63a..bd47a3bf 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -2,7 +2,7 @@ use std::{hint::black_box, sync::Arc, time::Duration}; use azalea::{ pathfinder::{ - astar::{self, a_star}, + astar::{self, a_star, PathfinderTimeout}, goals::{BlockPosGoal, Goal}, mining::MiningCache, world::CachedWorld, @@ -139,7 +139,7 @@ fn run_pathfinder_benchmark( |n| goal.heuristic(n), successors, |n| goal.success(n), - Duration::MAX, + PathfinderTimeout::Time(Duration::MAX), ); assert!(!partial); diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs index ae0808cb..ab323b2f 100644 --- a/azalea/examples/testbot/commands/debug.rs +++ b/azalea/examples/testbot/commands/debug.rs @@ -5,6 +5,7 @@ use azalea::{ entity::{LookDirection, Position}, interact::HitResultComponent, world::MinecraftEntityId, + BlockPos, }; use parking_lot::Mutex; @@ -102,4 +103,18 @@ pub fn register(commands: &mut CommandDispatcher>) { 1 })); + + commands.register(literal("getblock").then(argument("x", integer()).then( + argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + let x = get_integer(ctx, "x").unwrap(); + let y = get_integer(ctx, "y").unwrap(); + let z = get_integer(ctx, "z").unwrap(); + println!("getblock xyz {x} {y} {z}"); + let block_pos = BlockPos::new(x, y, z); + let block = source.bot.world().read().get_block_state(&block_pos); + source.reply(&format!("Block at {block_pos:?} is {block:?}")); + 1 + })), + ))); } diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs index 81cd2bb8..3d9f999e 100644 --- a/azalea/examples/testbot/main.rs +++ b/azalea/examples/testbot/main.rs @@ -1,13 +1,24 @@ //! A relatively simple bot for demonstrating some of Azalea's capabilities. //! -//! Usage: +//! ## Usage +//! //! - Modify the consts below if necessary. -//! - Run `cargo r --example testbot -- --owner --name -//! --address
`. +//! - Run `cargo r --example testbot -- [arguments]`. (see below) //! - Commands are prefixed with `!` in chat. You can send them either in public //! chat or as a /msg. //! - Some commands to try are `!goto`, `!killaura true`, `!down`. Check the //! `commands` directory to see all of them. +//! +//! ### Arguments +//! +//! - `--owner` or `-O`: The username of the player who owns the bot. The bot +//! will ignore commands from other players. +//! - `--name` or `-N`: The username or email of the bot. +//! - `--address` or `-A`: The address of the server to join. +//! - `--pathfinder-debug-particles` or `-P`: Whether the bot should run +//! /particle a ton of times to show where it's pathfinding to. You should +//! only have this on if the bot has operator permissions, otherwise it'll +//! just spam the server console unnecessarily. #![feature(async_closure)] #![feature(trivial_bounds)] @@ -28,11 +39,6 @@ use azalea::ClientInformation; use commands::{register_commands, CommandSource}; use parking_lot::Mutex; -/// Whether the bot should run /particle a ton of times to show where it's -/// pathfinding to. You should only have this on if the bot has operator -/// permissions, otherwise it'll just spam the server console unnecessarily. -const PATHFINDER_DEBUG_PARTICLES: bool = false; - #[tokio::main] async fn main() { let args = parse_args(); @@ -121,7 +127,7 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> anyhow::Resu ..Default::default() }) .await?; - if PATHFINDER_DEBUG_PARTICLES { + if state.args.pathfinder_debug_particles { bot.ecs .lock() .entity_mut(bot.entity) @@ -208,12 +214,14 @@ pub struct Args { pub owner: String, pub name: String, pub address: String, + pub pathfinder_debug_particles: bool, } fn parse_args() -> Args { let mut owner_username = None; let mut bot_username = None; let mut address = None; + let mut pathfinder_debug_particles = false; let mut args = env::args().skip(1); while let Some(arg) = args.next() { @@ -227,6 +235,9 @@ fn parse_args() -> Args { "--address" | "-A" => { address = args.next(); } + "--pathfinder-debug-particles" | "-P" => { + pathfinder_debug_particles = true; + } _ => { eprintln!("Unknown argument: {}", arg); process::exit(1); @@ -238,5 +249,6 @@ fn parse_args() -> Args { owner: owner_username.unwrap_or_else(|| "admin".to_string()), name: bot_username.unwrap_or_else(|| "azalea".to_string()), address: address.unwrap_or_else(|| "localhost".to_string()), + pathfinder_debug_particles, } } diff --git a/azalea/src/pathfinder/mining.rs b/azalea/src/pathfinder/mining.rs index 31b37b49..049e974a 100644 --- a/azalea/src/pathfinder/mining.rs +++ b/azalea/src/pathfinder/mining.rs @@ -1,6 +1,7 @@ use std::{cell::UnsafeCell, ops::RangeInclusive}; -use azalea_block::{BlockState, BlockStates}; +use azalea_block::{properties::Waterlogged, BlockState, BlockStates}; +use azalea_core::bitset::BitSet; use azalea_inventory::Menu; use nohash_hasher::IntMap; @@ -96,8 +97,12 @@ impl MiningCache { } pub fn is_liquid(&self, block: BlockState) -> bool { + // this already runs in about 1 nanosecond, so if you wanna try optimizing it at + // least run the benchmarks (in benches/checks.rs) + self.water_block_state_range.contains(&block.id) || self.lava_block_state_range.contains(&block.id) + || is_waterlogged(block) } pub fn is_falling_block(&self, block: BlockState) -> bool { @@ -106,3 +111,7 @@ impl MiningCache { .is_ok() } } + +pub fn is_waterlogged(block: BlockState) -> bool { + block.property::().unwrap_or_default() +} diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index b82b5f84..8ad0b1d0 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -594,7 +594,7 @@ pub fn check_node_reached( executing_path.path = executing_path.path.split_off(i + 1); executing_path.last_reached_node = movement.target; executing_path.last_node_reached_at = Instant::now(); - trace!("reached node {:?}", movement.target); + trace!("reached node {}", movement.target); if let Some(new_path) = executing_path.queued_path.take() { debug!( @@ -696,7 +696,7 @@ pub fn recalculate_near_end_of_path( && executing_path.is_path_partial { if let Some(goal) = pathfinder.goal.as_ref().cloned() { - debug!("Recalculating path because it ends soon"); + debug!("Recalculating path because it's empty or ends soon"); debug!( "recalculate_near_end_of_path executing_path.is_path_partial: {}", executing_path.is_path_partial @@ -953,7 +953,7 @@ mod tests { goal: Arc::new(BlockPosGoal(end_pos)), successors_fn: moves::default_move, allow_mining: false, - deterministic_timeout: false, + deterministic_timeout: true, }); simulation } @@ -1161,7 +1161,7 @@ mod tests { let mut simulation = setup_blockposgoal_simulation( &mut partial_chunks, BlockPos::new(0, 71, 0), - BlockPos::new(2, 74, 9), + BlockPos::new(4, 74, 9), vec![ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1), @@ -1169,9 +1169,11 @@ mod tests { BlockPos::new(0, 71, 3), BlockPos::new(0, 72, 6), BlockPos::new(0, 73, 9), + // this is the point where the bot might fall if it has too much momentum BlockPos::new(2, 73, 9), + BlockPos::new(4, 73, 9), ], ); - assert_simulation_reaches(&mut simulation, 80, BlockPos::new(2, 74, 9)); + assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 74, 9)); } }