From 0569862a1b1f46f1854f4eafd1569359397f2515 Mon Sep 17 00:00:00 2001 From: mat Date: Mon, 2 Jun 2025 03:14:08 -0430 Subject: [PATCH] fix issues related to pathfinder mining --- azalea-client/src/client.rs | 10 +- azalea-client/src/entity_query.rs | 2 +- azalea-client/src/lib.rs | 6 +- azalea-client/src/local_player.rs | 21 +- azalea-client/src/player.rs | 12 +- azalea-client/src/plugins/chunks.rs | 2 +- azalea-client/src/plugins/disconnect.rs | 5 +- azalea-client/src/plugins/events.rs | 2 +- azalea-client/src/plugins/interact.rs | 9 +- azalea-client/src/plugins/mining.rs | 180 ++++++++++-------- .../src/plugins/packet/config/mod.rs | 2 +- .../src/plugins/packet/game/events.rs | 2 +- azalea-client/src/plugins/packet/game/mod.rs | 7 +- azalea-client/src/plugins/packet/login/mod.rs | 4 +- azalea-client/src/test_simulation.rs | 4 +- .../login_to_dimension_with_same_name.rs | 2 +- azalea/benches/pathfinder.rs | 15 +- azalea/benches/physics.rs | 2 +- azalea/examples/testbot/commands.rs | 4 +- azalea/src/pathfinder/debug.rs | 2 +- azalea/src/pathfinder/mod.rs | 84 ++++++-- azalea/src/pathfinder/simulation.rs | 18 +- azalea/src/swarm/events.rs | 2 +- 23 files changed, 239 insertions(+), 158 deletions(-) diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 190e999c..7b63ff12 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -45,7 +45,7 @@ use tracing::{debug, error, info, warn}; use uuid::Uuid; use crate::{ - Account, DefaultPlugins, PlayerInfo, + Account, DefaultPlugins, attack::{self}, chunks::ChunkBatchInfo, connection::RawConnection, @@ -54,13 +54,11 @@ use crate::{ interact::CurrentSequenceNumber, inventory::Inventory, join::{ConnectOpts, StartJoinServerEvent}, - local_player::{ - GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList, - }, + local_player::{Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList}, mining::{self}, movement::{LastSentLookDirection, PhysicsState}, packet::game::SendPacketEvent, - player::retroactively_add_game_profile_component, + player::{GameProfileComponent, PlayerInfo, retroactively_add_game_profile_component}, }; /// `Client` has the things that a user interacting with the library will want. @@ -324,7 +322,7 @@ impl Client { /// This will panic if the component doesn't exist on the client. /// /// ``` - /// # use azalea_client::{Client, Hunger}; + /// # use azalea_client::{Client, local_player::Hunger}; /// # fn example(bot: &Client) { /// let hunger = bot.map_component::(|h| h.food); /// # } diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs index ef11813e..ad8a87a6 100644 --- a/azalea-client/src/entity_query.rs +++ b/azalea-client/src/entity_query.rs @@ -43,7 +43,7 @@ impl Client { /// /// # Example /// ``` - /// use azalea_client::{Client, GameProfileComponent}; + /// use azalea_client::{Client, player::GameProfileComponent}; /// use azalea_entity::{Position, metadata::Player}; /// use bevy_ecs::query::With; /// diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 31ad6e71..6bff353e 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -11,9 +11,9 @@ mod account; mod client; mod entity_query; -mod local_player; +pub mod local_player; pub mod ping; -mod player; +pub mod player; mod plugins; #[doc(hidden)] @@ -29,9 +29,7 @@ pub use client::{ StartClientOpts, start_ecs_runner, }; pub use events::Event; -pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList}; pub use movement::{ PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, }; -pub use player::PlayerInfo; pub use plugins::*; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index ee318c06..4a937ec7 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -4,7 +4,6 @@ use std::{ sync::{Arc, PoisonError}, }; -use azalea_auth::game_profile::GameProfile; use azalea_core::game_type::GameMode; use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities; use azalea_world::{Instance, PartialInstance}; @@ -16,7 +15,7 @@ use tokio::sync::mpsc; use tracing::error; use uuid::Uuid; -use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent}; +use crate::{ClientInformation, events::Event as AzaleaEvent, player::PlayerInfo}; /// A component that keeps strong references to our [`PartialInstance`] and /// [`Instance`] for local players. @@ -40,14 +39,6 @@ pub struct InstanceHolder { pub instance: Arc>, } -/// A component only present in players that contains the [`GameProfile`] (which -/// you can use to get a player's name). -/// -/// Note that it's possible for this to be missing in a player if the server -/// never sent the player info for them (though this is uncommon). -#[derive(Component, Clone, Debug, Deref, DerefMut)] -pub struct GameProfileComponent(pub GameProfile); - /// The gamemode of a local player. For a non-local player, you can look up the /// player in the [`TabList`]. #[derive(Component, Clone, Debug, Copy)] @@ -55,6 +46,14 @@ pub struct LocalGameMode { pub current: GameMode, pub previous: Option, } +impl From for LocalGameMode { + fn from(current: GameMode) -> Self { + LocalGameMode { + current, + previous: None, + } + } +} /// A component that contains the abilities the player has, like flying /// or instantly breaking blocks. This is only present on local players. @@ -92,7 +91,7 @@ pub struct PermissionLevel(pub u8); /// tab list. /// /// ``` -/// # use azalea_client::TabList; +/// # use azalea_client::local_player::TabList; /// # fn example(client: &azalea_client::Client) { /// let tab_list = client.component::(); /// println!("Online players:"); diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index d774e877..d696d133 100644 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -3,12 +3,14 @@ use azalea_chat::FormattedText; use azalea_core::game_type::GameMode; use azalea_entity::indexing::EntityUuidIndex; use bevy_ecs::{ + component::Component, event::EventReader, system::{Commands, Res}, }; +use derive_more::{Deref, DerefMut}; use uuid::Uuid; -use crate::{GameProfileComponent, packet::game::AddPlayerEvent}; +use crate::packet::game::AddPlayerEvent; /// A player in the tab list. #[derive(Debug, Clone)] @@ -29,6 +31,14 @@ pub struct PlayerInfo { pub display_name: Option>, } +/// A component only present in players that contains the [`GameProfile`] (which +/// you can use to get a player's name). +/// +/// Note that it's possible for this to be missing in a player if the server +/// never sent the player info for them (though this is uncommon). +#[derive(Component, Clone, Debug, Deref, DerefMut)] +pub struct GameProfileComponent(pub GameProfile); + /// Add a [`GameProfileComponent`] when an [`AddPlayerEvent`] is received. /// Usually the `GameProfileComponent` will be added from the /// `ClientboundGamePacket::AddPlayer` handler though. diff --git a/azalea-client/src/plugins/chunks.rs b/azalea-client/src/plugins/chunks.rs index 8b052f63..7a99c16c 100644 --- a/azalea-client/src/plugins/chunks.rs +++ b/azalea-client/src/plugins/chunks.rs @@ -17,7 +17,7 @@ use bevy_ecs::prelude::*; use tracing::{error, trace}; use crate::{ - InstanceHolder, interact::handle_start_use_item_queued, inventory::InventorySet, + interact::handle_start_use_item_queued, inventory::InventorySet, local_player::InstanceHolder, packet::game::SendPacketEvent, respawn::perform_respawn, }; diff --git a/azalea-client/src/plugins/disconnect.rs b/azalea-client/src/plugins/disconnect.rs index 6fa72faf..c6c01e35 100644 --- a/azalea-client/src/plugins/disconnect.rs +++ b/azalea-client/src/plugins/disconnect.rs @@ -9,7 +9,10 @@ use derive_more::Deref; use tracing::info; use super::login::IsAuthenticated; -use crate::{InstanceHolder, chat_signing, client::JoinedClientBundle, connection::RawConnection}; +use crate::{ + chat_signing, client::JoinedClientBundle, connection::RawConnection, + local_player::InstanceHolder, +}; pub struct DisconnectPlugin; impl Plugin for DisconnectPlugin { diff --git a/azalea-client/src/plugins/events.rs b/azalea-client/src/plugins/events.rs index 090b9d0d..d9cbf912 100644 --- a/azalea-client/src/plugins/events.rs +++ b/azalea-client/src/plugins/events.rs @@ -14,12 +14,12 @@ use derive_more::{Deref, DerefMut}; use tokio::sync::mpsc; use crate::{ - PlayerInfo, chat::{ChatPacket, ChatReceivedEvent}, disconnect::DisconnectEvent, packet::game::{ AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent, }, + player::PlayerInfo, }; // (for contributors): diff --git a/azalea-client/src/plugins/interact.rs b/azalea-client/src/plugins/interact.rs index 1baef2db..712e3242 100644 --- a/azalea-client/src/plugins/interact.rs +++ b/azalea-client/src/plugins/interact.rs @@ -24,7 +24,7 @@ use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; use tracing::warn; -use super::mining::{Mining, MiningSet}; +use super::mining::Mining; use crate::{ Client, attack::handle_attack_event, @@ -58,12 +58,7 @@ impl Plugin for InteractPlugin { .after(MoveEventsSet), ), ) - .add_systems( - GameTick, - handle_start_use_item_queued - .after(MiningSet) - .before(PhysicsSet), - ) + .add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet)) .add_observer(handle_swing_arm_trigger); } } diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs index 260f7140..204b482c 100644 --- a/azalea-client/src/plugins/mining.rs +++ b/azalea-client/src/plugins/mining.rs @@ -8,16 +8,16 @@ use azalea_world::{InstanceContainer, InstanceName}; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; -use tracing::trace; +use tracing::{info, trace}; use crate::{ - Client, InstanceHolder, + Client, interact::{ CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks, check_is_interaction_restricted, }, inventory::{Inventory, InventorySet}, - local_player::{LocalGameMode, PermissionLevel, PlayerAbilities}, + local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities}, movement::MoveEventsSet, packet::game::SendPacketEvent, }; @@ -40,14 +40,15 @@ impl Plugin for MiningPlugin { handle_mining_queued, ) .chain() - .before(PhysicsSet) + .after(PhysicsSet) + .after(super::movement::send_position) + .after(super::attack::handle_attack_queued) .in_set(MiningSet), ) .add_systems( Update, ( handle_start_mining_block_event, - handle_finish_mining_block_event, handle_stop_mining_block_event, ) .chain() @@ -60,7 +61,8 @@ impl Plugin for MiningPlugin { .after(crate::attack::handle_attack_event) .after(crate::interact::handle_start_use_item_queued) .before(crate::interact::handle_swing_arm_event), - ); + ) + .add_observer(handle_finish_mining_block_observer); } } @@ -153,13 +155,15 @@ fn handle_auto_mine( pub struct Mining { pub pos: BlockPos, pub dir: Direction, + /// See [`MiningQueued::force`]. + pub force: bool, } /// Start mining the block at the given position. /// /// If we're looking at the block then the correct direction will be used, /// otherwise it'll be [`Direction::Down`]. -#[derive(Event)] +#[derive(Event, Debug)] pub struct StartMiningBlockEvent { pub entity: Entity, pub position: BlockPos, @@ -170,33 +174,37 @@ fn handle_start_mining_block_event( mut query: Query<&HitResultComponent>, ) { for event in events.read() { + trace!("{event:?}"); let hit_result = query.get_mut(event.entity).unwrap(); - let direction = if let Some(block_hit_result) = hit_result.as_block_hit_result_if_not_miss() + let (direction, force) = if let Some(block_hit_result) = + hit_result.as_block_hit_result_if_not_miss() && block_hit_result.block_pos == event.position { // we're looking at the block - block_hit_result.direction + (block_hit_result.direction, false) } else { // we're not looking at the block, arbitrary direction - Direction::Down + (Direction::Down, true) }; commands.entity(event.entity).insert(MiningQueued { position: event.position, direction, + force, }); } } /// Present on entities when they're going to start mining a block next tick. -#[derive(Component)] +#[derive(Component, Debug, Clone)] pub struct MiningQueued { pub position: BlockPos, pub direction: Direction, + /// Whether we should mine the block regardless of whether it's reachable. + pub force: bool, } #[allow(clippy::too_many_arguments, clippy::type_complexity)] fn handle_mining_queued( mut commands: Commands, - mut finish_mining_events: EventWriter, mut attack_block_events: EventWriter, mut mine_block_progress_events: EventWriter, query: Query<( @@ -233,6 +241,7 @@ fn handle_mining_queued( mut current_mining_pos, ) in query { + info!("mining_queued: {mining_queued:?}"); commands.entity(entity).remove::(); let instance = instance_holder.instance.read(); @@ -248,10 +257,12 @@ fn handle_mining_queued( // is outside of the worldborder if game_mode.current == GameMode::Creative { - finish_mining_events.write(FinishMiningBlockEvent { + commands.trigger_targets( + FinishMiningBlockEvent { + position: mining_queued.position, + }, entity, - position: mining_queued.position, - }); + ); **mine_delay = 5; } else if mining.is_none() || !is_same_mining_target( @@ -304,14 +315,17 @@ fn handle_mining_queued( ) >= 1. { // block was broken instantly - finish_mining_events.write(FinishMiningBlockEvent { + commands.trigger_targets( + FinishMiningBlockEvent { + position: mining_queued.position, + }, entity, - position: mining_queued.position, - }); + ); } else { let mining = Mining { pos: mining_queued.position, dir: mining_queued.direction, + force: mining_queued.force, }; trace!("inserting mining component {mining:?} for entity {entity:?}"); commands.entity(entity).insert(mining); @@ -370,7 +384,7 @@ fn is_same_mining_target( } /// A component bundle for players that can mine blocks. -#[derive(Bundle, Default)] +#[derive(Bundle, Default, Clone)] pub struct MineBundle { pub delay: MineDelay, pub progress: MineProgress, @@ -380,12 +394,12 @@ pub struct MineBundle { } /// A component that counts down until we start mining the next block. -#[derive(Component, Debug, Default, Deref, DerefMut)] +#[derive(Component, Debug, Default, Deref, DerefMut, Clone)] pub struct MineDelay(pub u32); /// A component that stores the progress of the current mining operation. This /// is a value between 0 and 1. -#[derive(Component, Debug, Default, Deref, DerefMut)] +#[derive(Component, Debug, Default, Deref, DerefMut, Clone)] pub struct MineProgress(pub f32); impl MineProgress { @@ -413,15 +427,14 @@ pub struct MineBlockPos(pub Option); #[derive(Component, Clone, Debug, Default, Deref, DerefMut)] pub struct MineItem(pub ItemStack); -/// Sent when we completed mining a block. +/// A trigger that's sent when we completed mining a block. #[derive(Event)] pub struct FinishMiningBlockEvent { - pub entity: Entity, pub position: BlockPos, } -pub fn handle_finish_mining_block_event( - mut events: EventReader, +pub fn handle_finish_mining_block_observer( + trigger: Trigger, mut query: Query<( &InstanceName, &LocalGameMode, @@ -432,53 +445,48 @@ pub fn handle_finish_mining_block_event( )>, instances: Res, ) { - for event in events.read() { - let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) = - query.get_mut(event.entity).unwrap(); - let instance_lock = instances.get(instance_name).unwrap(); - let instance = instance_lock.read(); - if check_is_interaction_restricted( - &instance, - &event.position, - &game_mode.current, - inventory, - ) { - continue; - } + let event = trigger.event(); - if game_mode.current == GameMode::Creative { - let held_item = inventory.held_item().kind(); - if matches!( - held_item, - azalea_registry::Item::Trident | azalea_registry::Item::DebugStick - ) || azalea_registry::tags::items::SWORDS.contains(&held_item) - { - continue; - } - } - - let Some(block_state) = instance.get_block_state(&event.position) else { - continue; - }; - - let registry_block = Box::::from(block_state).as_registry_block(); - if !can_use_game_master_blocks(abilities, permission_level) - && matches!( - registry_block, - azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock - ) - { - continue; - } - if block_state == BlockState::AIR { - continue; - } - - // when we break a waterlogged block we want to keep the water there - let fluid_state = FluidState::from(block_state); - let block_state_for_fluid = BlockState::from(fluid_state); - instance.set_block_state(&event.position, block_state_for_fluid); + let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) = + query.get_mut(trigger.target()).unwrap(); + let instance_lock = instances.get(instance_name).unwrap(); + let instance = instance_lock.read(); + if check_is_interaction_restricted(&instance, &event.position, &game_mode.current, inventory) { + return; } + + if game_mode.current == GameMode::Creative { + let held_item = inventory.held_item().kind(); + if matches!( + held_item, + azalea_registry::Item::Trident | azalea_registry::Item::DebugStick + ) || azalea_registry::tags::items::SWORDS.contains(&held_item) + { + return; + } + } + + let Some(block_state) = instance.get_block_state(&event.position) else { + return; + }; + + let registry_block = Box::::from(block_state).as_registry_block(); + if !can_use_game_master_blocks(abilities, permission_level) + && matches!( + registry_block, + azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock + ) + { + return; + } + if block_state == BlockState::AIR { + return; + } + + // when we break a waterlogged block we want to keep the water there + let fluid_state = FluidState::from(block_state); + let block_state_for_fluid = BlockState::from(fluid_state); + instance.set_block_state(&event.position, block_state_for_fluid); } /// Abort mining a block. @@ -535,7 +543,6 @@ pub fn continue_mining_block( )>, mut commands: Commands, mut mine_block_progress_events: EventWriter, - mut finish_mining_events: EventWriter, instances: Res, ) { for ( @@ -562,10 +569,12 @@ pub fn continue_mining_block( if game_mode.current == GameMode::Creative { // TODO: worldborder check **mine_delay = 5; - finish_mining_events.write(FinishMiningBlockEvent { + commands.trigger_targets( + FinishMiningBlockEvent { + position: mining.pos, + }, entity, - position: mining.pos, - }); + ); commands.trigger(SendPacketEvent::new( entity, ServerboundPlayerAction { @@ -576,12 +585,14 @@ pub fn continue_mining_block( }, )); commands.trigger(SwingArmEvent { entity }); - } else if is_same_mining_target( - mining.pos, - inventory, - current_mining_pos, - current_mining_item, - ) { + } else if mining.force + || is_same_mining_target( + mining.pos, + inventory, + current_mining_pos, + current_mining_item, + ) + { trace!("continue mining block at {:?}", mining.pos); let instance_lock = instances.get(instance_name).unwrap(); let instance = instance_lock.read(); @@ -612,10 +623,12 @@ pub fn continue_mining_block( // repeatedly inserts MiningQueued commands.entity(entity).remove::<(Mining, MiningQueued)>(); trace!("finished mining block at {:?}", mining.pos); - finish_mining_events.write(FinishMiningBlockEvent { + commands.trigger_targets( + FinishMiningBlockEvent { + position: mining.pos, + }, entity, - position: mining.pos, - }); + ); commands.trigger(SendPacketEvent::new( entity, ServerboundPlayerAction { @@ -641,6 +654,7 @@ pub fn continue_mining_block( commands.entity(entity).insert(MiningQueued { position: mining.pos, direction: mining.dir, + force: false, }); } } diff --git a/azalea-client/src/plugins/packet/config/mod.rs b/azalea-client/src/plugins/packet/config/mod.rs index 9c05705f..afe02159 100644 --- a/azalea-client/src/plugins/packet/config/mod.rs +++ b/azalea-client/src/plugins/packet/config/mod.rs @@ -13,11 +13,11 @@ use tracing::{debug, warn}; use super::as_system; use crate::{ - InstanceHolder, client::InConfigState, connection::RawConnection, declare_packet_handlers, disconnect::DisconnectEvent, + local_player::InstanceHolder, packet::game::{KeepAliveEvent, ResourcePackEvent}, }; diff --git a/azalea-client/src/plugins/packet/game/events.rs b/azalea-client/src/plugins/packet/game/events.rs index fae5a0f8..7134a2f2 100644 --- a/azalea-client/src/plugins/packet/game/events.rs +++ b/azalea-client/src/plugins/packet/game/events.rs @@ -12,7 +12,7 @@ use parking_lot::RwLock; use tracing::{error, trace}; use uuid::Uuid; -use crate::{PlayerInfo, client::InGameState, connection::RawConnection}; +use crate::{client::InGameState, connection::RawConnection, player::PlayerInfo}; /// An event that's sent when we receive a packet. /// ``` diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index 7c93aa9b..63c1bafa 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -20,7 +20,7 @@ pub use events::*; use tracing::{debug, error, trace, warn}; use crate::{ - ClientInformation, PlayerInfo, + ClientInformation, chat::{ChatPacket, ChatReceivedEvent}, chunks, connection::RawConnection, @@ -29,11 +29,10 @@ use crate::{ inventory::{ ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, }, - local_player::{ - GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList, - }, + local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList}, movement::{KnockbackEvent, KnockbackType}, packet::as_system, + player::{GameProfileComponent, PlayerInfo}, }; pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) { diff --git a/azalea-client/src/plugins/packet/login/mod.rs b/azalea-client/src/plugins/packet/login/mod.rs index 3dad17ab..99ebae9b 100644 --- a/azalea-client/src/plugins/packet/login/mod.rs +++ b/azalea-client/src/plugins/packet/login/mod.rs @@ -17,8 +17,8 @@ use tracing::{debug, error}; use super::as_system; use crate::{ - Account, GameProfileComponent, InConfigState, connection::RawConnection, - declare_packet_handlers, disconnect::DisconnectEvent, + Account, InConfigState, connection::RawConnection, declare_packet_handlers, + disconnect::DisconnectEvent, player::GameProfileComponent, }; pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundLoginPacket) { diff --git a/azalea-client/src/test_simulation.rs b/azalea-client/src/test_simulation.rs index 3f7cd8b2..c53a624a 100644 --- a/azalea-client/src/test_simulation.rs +++ b/azalea-client/src/test_simulation.rs @@ -30,8 +30,8 @@ use simdnbt::owned::{NbtCompound, NbtTag}; use uuid::Uuid; use crate::{ - ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle, - connection::RawConnection, disconnect::DisconnectEvent, + ClientInformation, InConfigState, LocalPlayerBundle, connection::RawConnection, + disconnect::DisconnectEvent, local_player::InstanceHolder, player::GameProfileComponent, }; /// A way to simulate a client in a server, used for some internal tests. diff --git a/azalea-client/tests/login_to_dimension_with_same_name.rs b/azalea-client/tests/login_to_dimension_with_same_name.rs index be362bb7..24637693 100644 --- a/azalea-client/tests/login_to_dimension_with_same_name.rs +++ b/azalea-client/tests/login_to_dimension_with_same_name.rs @@ -1,4 +1,4 @@ -use azalea_client::{InConfigState, InGameState, InstanceHolder, test_simulation::*}; +use azalea_client::{InConfigState, InGameState, local_player::InstanceHolder, test_simulation::*}; use azalea_core::{position::ChunkPos, resource_location::ResourceLocation}; use azalea_entity::LocalEntity; use azalea_protocol::packets::{ diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index bb4e312a..cddaee2c 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -4,6 +4,7 @@ use azalea::{ BlockPos, pathfinder::{ astar::{self, PathfinderTimeout, WeightedNode, a_star}, + custom_state::CustomPathfinderStateRef, goals::{BlockPosGoal, Goal}, mining::MiningCache, rel_block_pos::RelBlockPos, @@ -41,13 +42,13 @@ fn generate_bedrock_world( let mut chunk = chunk.write(); for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::Bedrock.into(), chunks.min_y, ); if rng.gen_bool(0.5) { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 2, z), azalea_registry::Block::Bedrock.into(), chunks.min_y, @@ -99,7 +100,7 @@ fn generate_mining_world( for y in chunks.min_y..(chunks.min_y + chunks.height as i32) { for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, y, z), azalea_registry::Block::Stone.into(), chunks.min_y, @@ -134,7 +135,13 @@ fn run_pathfinder_benchmark( let goal = BlockPosGoal(end); let successors = |pos: RelBlockPos| { - azalea::pathfinder::call_successors_fn(&cached_world, &mining_cache, successors_fn, pos) + azalea::pathfinder::call_successors_fn( + &cached_world, + &mining_cache, + &CustomPathfinderStateRef::default(), + successors_fn, + pos, + ) }; let astar::Path { diff --git a/azalea/benches/physics.rs b/azalea/benches/physics.rs index 5eb164d7..2f122014 100644 --- a/azalea/benches/physics.rs +++ b/azalea/benches/physics.rs @@ -25,7 +25,7 @@ fn generate_world(partial_chunks: &mut PartialChunkStorage, size: u32) -> ChunkS let mut chunk = chunk.write(); for x in 0..16_u8 { for z in 0..16_u8 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::OakFence.into(), chunks.min_y, diff --git a/azalea/examples/testbot/commands.rs b/azalea/examples/testbot/commands.rs index da6b7cc2..79a73bd9 100644 --- a/azalea/examples/testbot/commands.rs +++ b/azalea/examples/testbot/commands.rs @@ -3,8 +3,8 @@ pub mod debug; pub mod movement; use azalea::{ - Client, GameProfileComponent, brigadier::prelude::*, chat::ChatPacket, ecs::prelude::*, - entity::metadata::Player, + Client, brigadier::prelude::*, chat::ChatPacket, ecs::prelude::*, entity::metadata::Player, + player::GameProfileComponent, }; use parking_lot::Mutex; diff --git a/azalea/src/pathfinder/debug.rs b/azalea/src/pathfinder/debug.rs index 72423243..b00e4272 100644 --- a/azalea/src/pathfinder/debug.rs +++ b/azalea/src/pathfinder/debug.rs @@ -1,4 +1,4 @@ -use azalea_client::{InstanceHolder, chat::SendChatEvent}; +use azalea_client::{chat::SendChatEvent, local_player::InstanceHolder}; use azalea_core::position::Vec3; use bevy_ecs::prelude::*; diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 94d5bc69..08c72f9a 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -27,9 +27,10 @@ use std::{ use astar::{Edge, PathfinderTimeout}; use azalea_client::{ - InstanceHolder, StartSprintEvent, StartWalkEvent, + StartSprintEvent, StartWalkEvent, inventory::{Inventory, InventorySet, SetSelectedHotbarSlotEvent}, - mining::{Mining, StartMiningBlockEvent}, + local_player::InstanceHolder, + mining::{Mining, MiningSet, StartMiningBlockEvent}, movement::MoveEventsSet, }; use azalea_core::{position::BlockPos, tick::GameTick}; @@ -89,7 +90,8 @@ impl Plugin for PathfinderPlugin { ) .chain() .after(PhysicsSet) - .after(azalea_client::movement::send_position), + .after(azalea_client::movement::send_position) + .after(MiningSet), ) .add_systems(PreUpdate, add_default_pathfinder) .add_systems( @@ -1271,6 +1273,7 @@ mod tests { time::{Duration, Instant}, }; + use azalea_block::BlockState; use azalea_core::position::{BlockPos, ChunkPos, Vec3}; use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; @@ -1286,9 +1289,9 @@ mod tests { partial_chunks: &mut PartialChunkStorage, start_pos: BlockPos, end_pos: BlockPos, - solid_blocks: Vec, + solid_blocks: &[BlockPos], ) -> Simulation { - let mut simulation = setup_simulation_world(partial_chunks, start_pos, solid_blocks); + 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 { @@ -1311,10 +1314,14 @@ mod tests { fn setup_simulation_world( partial_chunks: &mut PartialChunkStorage, start_pos: BlockPos, - solid_blocks: Vec, + solid_blocks: &[BlockPos], + extra_blocks: &[(BlockPos, BlockState)], ) -> Simulation { let mut chunk_positions = HashSet::new(); - for block_pos in &solid_blocks { + for block_pos in solid_blocks { + chunk_positions.insert(ChunkPos::from(block_pos)); + } + for (block_pos, _) in extra_blocks { chunk_positions.insert(ChunkPos::from(block_pos)); } @@ -1323,8 +1330,12 @@ mod tests { partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); } for block_pos in solid_blocks { - chunks.set_block_state(&block_pos, azalea_registry::Block::Stone.into()); + chunks.set_block_state(block_pos, azalea_registry::Block::Stone.into()); } + for (block_pos, block_state) in extra_blocks { + chunks.set_block_state(block_pos, *block_state); + } + let player = SimulatedPlayerBundle::new(Vec3::new( start_pos.x as f64 + 0.5, start_pos.y as f64, @@ -1360,7 +1371,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 1), - vec![BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1)], + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1)], ); assert_simulation_reaches(&mut simulation, 20, BlockPos::new(0, 71, 1)); } @@ -1372,7 +1383,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(2, 71, 2), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(1, 70, 1), BlockPos::new(2, 70, 2), @@ -1390,7 +1401,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 3), BlockPos::new(5, 76, 0), - vec![ + &[ BlockPos::new(0, 70, 3), BlockPos::new(0, 70, 2), BlockPos::new(0, 70, 1), @@ -1412,7 +1423,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 3), - vec![BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 3)], + &[BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 3)], ); assert_simulation_reaches(&mut simulation, 40, BlockPos::new(0, 71, 3)); } @@ -1424,7 +1435,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(3, 67, 4), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 69, 1), BlockPos::new(0, 68, 2), @@ -1443,7 +1454,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 70, 5), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1), BlockPos::new(0, 69, 2), @@ -1460,7 +1471,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(0, 68, 3), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 69, 1), BlockPos::new(0, 68, 2), @@ -1477,7 +1488,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(3, 74, 0), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 71, 3), BlockPos::new(3, 72, 3), @@ -1494,7 +1505,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(4, 71, 12), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 4), BlockPos::new(0, 70, 8), @@ -1512,7 +1523,7 @@ mod tests { &mut partial_chunks, BlockPos::new(0, 71, 0), BlockPos::new(4, 74, 9), - vec![ + &[ BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 1), BlockPos::new(0, 70, 2), @@ -1526,4 +1537,41 @@ mod tests { ); assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 74, 9)); } + + #[test] + fn test_mine_through_non_colliding_block() { + let mut partial_chunks = PartialChunkStorage::default(); + + let mut simulation = setup_simulation_world( + &mut partial_chunks, + // the pathfinder can't actually dig straight down, so we start a block to the side so + // it can descend correctly + BlockPos::new(0, 72, 1), + &[BlockPos::new(0, 71, 1)], + &[ + ( + BlockPos::new(0, 71, 0), + azalea_registry::Block::SculkVein.into(), + ), + ( + BlockPos::new(0, 70, 0), + azalea_registry::Block::GrassBlock.into(), + ), + // this is an extra check to make sure that we don't accidentally break the block + // below (since tnt will break instantly) + (BlockPos::new(0, 69, 0), azalea_registry::Block::Tnt.into()), + ], + ); + + simulation.app.world_mut().send_event(GotoEvent { + entity: simulation.entity, + goal: Arc::new(BlockPosGoal(BlockPos::new(0, 70, 0))), + successors_fn: moves::default_move, + allow_mining: true, + min_timeout: PathfinderTimeout::Nodes(1_000_000), + max_timeout: PathfinderTimeout::Nodes(5_000_000), + }); + + assert_simulation_reaches(&mut simulation, 200, BlockPos::new(0, 70, 0)); + } } diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 5a68bf88..337efda7 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -2,8 +2,13 @@ use std::sync::Arc; -use azalea_client::{PhysicsState, inventory::Inventory, packet::game::SendPacketEvent}; -use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; +use azalea_client::{ + PhysicsState, interact::CurrentSequenceNumber, inventory::Inventory, + local_player::LocalGameMode, mining::MineBundle, packet::game::SendPacketEvent, +}; +use azalea_core::{ + game_type::GameMode, position::Vec3, resource_location::ResourceLocation, tick::GameTick, +}; use azalea_entity::{ Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance, }; @@ -87,7 +92,7 @@ fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc>, player: &SimulatedPlayerBundle, -) -> impl Bundle + use<> { +) -> impl Bundle { let instance_name = simulation_instance_name(); ( @@ -100,12 +105,17 @@ fn create_simulation_player_complete_bundle( azalea_registry::EntityKind::Player, instance_name, ), - azalea_client::InstanceHolder { + azalea_client::local_player::InstanceHolder { // partial_instance is never actually used by the pathfinder so partial_instance: Arc::new(RwLock::new(PartialInstance::default())), instance: instance.clone(), }, Inventory::default(), + LocalGameMode::from(GameMode::Survival), + MineBundle::default(), + CurrentSequenceNumber::default(), + azalea_client::local_player::PermissionLevel::default(), + azalea_client::local_player::PlayerAbilities::default(), ) } diff --git a/azalea/src/swarm/events.rs b/azalea/src/swarm/events.rs index b8154c50..aff578a3 100644 --- a/azalea/src/swarm/events.rs +++ b/azalea/src/swarm/events.rs @@ -1,4 +1,4 @@ -use azalea_client::InstanceHolder; +use azalea_client::local_player::InstanceHolder; use azalea_world::MinecraftEntityId; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*;