1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

fix issues related to pathfinder mining

This commit is contained in:
mat 2025-06-02 03:14:08 -04:30
parent d7cd305059
commit 0569862a1b
23 changed files with 239 additions and 158 deletions

View file

@ -45,7 +45,7 @@ use tracing::{debug, error, info, warn};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
Account, DefaultPlugins, PlayerInfo, Account, DefaultPlugins,
attack::{self}, attack::{self},
chunks::ChunkBatchInfo, chunks::ChunkBatchInfo,
connection::RawConnection, connection::RawConnection,
@ -54,13 +54,11 @@ use crate::{
interact::CurrentSequenceNumber, interact::CurrentSequenceNumber,
inventory::Inventory, inventory::Inventory,
join::{ConnectOpts, StartJoinServerEvent}, join::{ConnectOpts, StartJoinServerEvent},
local_player::{ local_player::{Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList},
GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, PlayerAbilities, TabList,
},
mining::{self}, mining::{self},
movement::{LastSentLookDirection, PhysicsState}, movement::{LastSentLookDirection, PhysicsState},
packet::game::SendPacketEvent, 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. /// `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. /// 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) { /// # fn example(bot: &Client) {
/// let hunger = bot.map_component::<Hunger, _>(|h| h.food); /// let hunger = bot.map_component::<Hunger, _>(|h| h.food);
/// # } /// # }

View file

@ -43,7 +43,7 @@ impl Client {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// use azalea_client::{Client, GameProfileComponent}; /// use azalea_client::{Client, player::GameProfileComponent};
/// use azalea_entity::{Position, metadata::Player}; /// use azalea_entity::{Position, metadata::Player};
/// use bevy_ecs::query::With; /// use bevy_ecs::query::With;
/// ///

View file

@ -11,9 +11,9 @@
mod account; mod account;
mod client; mod client;
mod entity_query; mod entity_query;
mod local_player; pub mod local_player;
pub mod ping; pub mod ping;
mod player; pub mod player;
mod plugins; mod plugins;
#[doc(hidden)] #[doc(hidden)]
@ -29,9 +29,7 @@ pub use client::{
StartClientOpts, start_ecs_runner, StartClientOpts, start_ecs_runner,
}; };
pub use events::Event; pub use events::Event;
pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList};
pub use movement::{ pub use movement::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
}; };
pub use player::PlayerInfo;
pub use plugins::*; pub use plugins::*;

View file

@ -4,7 +4,6 @@ use std::{
sync::{Arc, PoisonError}, sync::{Arc, PoisonError},
}; };
use azalea_auth::game_profile::GameProfile;
use azalea_core::game_type::GameMode; use azalea_core::game_type::GameMode;
use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities; use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
use azalea_world::{Instance, PartialInstance}; use azalea_world::{Instance, PartialInstance};
@ -16,7 +15,7 @@ use tokio::sync::mpsc;
use tracing::error; use tracing::error;
use uuid::Uuid; 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 /// A component that keeps strong references to our [`PartialInstance`] and
/// [`Instance`] for local players. /// [`Instance`] for local players.
@ -40,14 +39,6 @@ pub struct InstanceHolder {
pub instance: Arc<RwLock<Instance>>, pub instance: Arc<RwLock<Instance>>,
} }
/// 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 /// The gamemode of a local player. For a non-local player, you can look up the
/// player in the [`TabList`]. /// player in the [`TabList`].
#[derive(Component, Clone, Debug, Copy)] #[derive(Component, Clone, Debug, Copy)]
@ -55,6 +46,14 @@ pub struct LocalGameMode {
pub current: GameMode, pub current: GameMode,
pub previous: Option<GameMode>, pub previous: Option<GameMode>,
} }
impl From<GameMode> for LocalGameMode {
fn from(current: GameMode) -> Self {
LocalGameMode {
current,
previous: None,
}
}
}
/// A component that contains the abilities the player has, like flying /// A component that contains the abilities the player has, like flying
/// or instantly breaking blocks. This is only present on local players. /// or instantly breaking blocks. This is only present on local players.
@ -92,7 +91,7 @@ pub struct PermissionLevel(pub u8);
/// tab list. /// tab list.
/// ///
/// ``` /// ```
/// # use azalea_client::TabList; /// # use azalea_client::local_player::TabList;
/// # fn example(client: &azalea_client::Client) { /// # fn example(client: &azalea_client::Client) {
/// let tab_list = client.component::<TabList>(); /// let tab_list = client.component::<TabList>();
/// println!("Online players:"); /// println!("Online players:");

View file

@ -3,12 +3,14 @@ use azalea_chat::FormattedText;
use azalea_core::game_type::GameMode; use azalea_core::game_type::GameMode;
use azalea_entity::indexing::EntityUuidIndex; use azalea_entity::indexing::EntityUuidIndex;
use bevy_ecs::{ use bevy_ecs::{
component::Component,
event::EventReader, event::EventReader,
system::{Commands, Res}, system::{Commands, Res},
}; };
use derive_more::{Deref, DerefMut};
use uuid::Uuid; use uuid::Uuid;
use crate::{GameProfileComponent, packet::game::AddPlayerEvent}; use crate::packet::game::AddPlayerEvent;
/// A player in the tab list. /// A player in the tab list.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -29,6 +31,14 @@ pub struct PlayerInfo {
pub display_name: Option<Box<FormattedText>>, pub display_name: Option<Box<FormattedText>>,
} }
/// 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. /// Add a [`GameProfileComponent`] when an [`AddPlayerEvent`] is received.
/// Usually the `GameProfileComponent` will be added from the /// Usually the `GameProfileComponent` will be added from the
/// `ClientboundGamePacket::AddPlayer` handler though. /// `ClientboundGamePacket::AddPlayer` handler though.

View file

@ -17,7 +17,7 @@ use bevy_ecs::prelude::*;
use tracing::{error, trace}; use tracing::{error, trace};
use crate::{ 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, packet::game::SendPacketEvent, respawn::perform_respawn,
}; };

View file

@ -9,7 +9,10 @@ use derive_more::Deref;
use tracing::info; use tracing::info;
use super::login::IsAuthenticated; 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; pub struct DisconnectPlugin;
impl Plugin for DisconnectPlugin { impl Plugin for DisconnectPlugin {

View file

@ -14,12 +14,12 @@ use derive_more::{Deref, DerefMut};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::{ use crate::{
PlayerInfo,
chat::{ChatPacket, ChatReceivedEvent}, chat::{ChatPacket, ChatReceivedEvent},
disconnect::DisconnectEvent, disconnect::DisconnectEvent,
packet::game::{ packet::game::{
AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent, AddPlayerEvent, DeathEvent, KeepAliveEvent, RemovePlayerEvent, UpdatePlayerEvent,
}, },
player::PlayerInfo,
}; };
// (for contributors): // (for contributors):

View file

@ -24,7 +24,7 @@ use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use tracing::warn; use tracing::warn;
use super::mining::{Mining, MiningSet}; use super::mining::Mining;
use crate::{ use crate::{
Client, Client,
attack::handle_attack_event, attack::handle_attack_event,
@ -58,12 +58,7 @@ impl Plugin for InteractPlugin {
.after(MoveEventsSet), .after(MoveEventsSet),
), ),
) )
.add_systems( .add_systems(GameTick, handle_start_use_item_queued.before(PhysicsSet))
GameTick,
handle_start_use_item_queued
.after(MiningSet)
.before(PhysicsSet),
)
.add_observer(handle_swing_arm_trigger); .add_observer(handle_swing_arm_trigger);
} }
} }

View file

@ -8,16 +8,16 @@ use azalea_world::{InstanceContainer, InstanceName};
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use tracing::trace; use tracing::{info, trace};
use crate::{ use crate::{
Client, InstanceHolder, Client,
interact::{ interact::{
CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks, CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
check_is_interaction_restricted, check_is_interaction_restricted,
}, },
inventory::{Inventory, InventorySet}, inventory::{Inventory, InventorySet},
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities}, local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities},
movement::MoveEventsSet, movement::MoveEventsSet,
packet::game::SendPacketEvent, packet::game::SendPacketEvent,
}; };
@ -40,14 +40,15 @@ impl Plugin for MiningPlugin {
handle_mining_queued, handle_mining_queued,
) )
.chain() .chain()
.before(PhysicsSet) .after(PhysicsSet)
.after(super::movement::send_position)
.after(super::attack::handle_attack_queued)
.in_set(MiningSet), .in_set(MiningSet),
) )
.add_systems( .add_systems(
Update, Update,
( (
handle_start_mining_block_event, handle_start_mining_block_event,
handle_finish_mining_block_event,
handle_stop_mining_block_event, handle_stop_mining_block_event,
) )
.chain() .chain()
@ -60,7 +61,8 @@ impl Plugin for MiningPlugin {
.after(crate::attack::handle_attack_event) .after(crate::attack::handle_attack_event)
.after(crate::interact::handle_start_use_item_queued) .after(crate::interact::handle_start_use_item_queued)
.before(crate::interact::handle_swing_arm_event), .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 struct Mining {
pub pos: BlockPos, pub pos: BlockPos,
pub dir: Direction, pub dir: Direction,
/// See [`MiningQueued::force`].
pub force: bool,
} }
/// Start mining the block at the given position. /// Start mining the block at the given position.
/// ///
/// If we're looking at the block then the correct direction will be used, /// If we're looking at the block then the correct direction will be used,
/// otherwise it'll be [`Direction::Down`]. /// otherwise it'll be [`Direction::Down`].
#[derive(Event)] #[derive(Event, Debug)]
pub struct StartMiningBlockEvent { pub struct StartMiningBlockEvent {
pub entity: Entity, pub entity: Entity,
pub position: BlockPos, pub position: BlockPos,
@ -170,33 +174,37 @@ fn handle_start_mining_block_event(
mut query: Query<&HitResultComponent>, mut query: Query<&HitResultComponent>,
) { ) {
for event in events.read() { for event in events.read() {
trace!("{event:?}");
let hit_result = query.get_mut(event.entity).unwrap(); 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 && block_hit_result.block_pos == event.position
{ {
// we're looking at the block // we're looking at the block
block_hit_result.direction (block_hit_result.direction, false)
} else { } else {
// we're not looking at the block, arbitrary direction // we're not looking at the block, arbitrary direction
Direction::Down (Direction::Down, true)
}; };
commands.entity(event.entity).insert(MiningQueued { commands.entity(event.entity).insert(MiningQueued {
position: event.position, position: event.position,
direction, direction,
force,
}); });
} }
} }
/// Present on entities when they're going to start mining a block next tick. /// Present on entities when they're going to start mining a block next tick.
#[derive(Component)] #[derive(Component, Debug, Clone)]
pub struct MiningQueued { pub struct MiningQueued {
pub position: BlockPos, pub position: BlockPos,
pub direction: Direction, 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)] #[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn handle_mining_queued( fn handle_mining_queued(
mut commands: Commands, mut commands: Commands,
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
mut attack_block_events: EventWriter<AttackBlockEvent>, mut attack_block_events: EventWriter<AttackBlockEvent>,
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>, mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
query: Query<( query: Query<(
@ -233,6 +241,7 @@ fn handle_mining_queued(
mut current_mining_pos, mut current_mining_pos,
) in query ) in query
{ {
info!("mining_queued: {mining_queued:?}");
commands.entity(entity).remove::<MiningQueued>(); commands.entity(entity).remove::<MiningQueued>();
let instance = instance_holder.instance.read(); let instance = instance_holder.instance.read();
@ -248,10 +257,12 @@ fn handle_mining_queued(
// is outside of the worldborder // is outside of the worldborder
if game_mode.current == GameMode::Creative { if game_mode.current == GameMode::Creative {
finish_mining_events.write(FinishMiningBlockEvent { commands.trigger_targets(
FinishMiningBlockEvent {
position: mining_queued.position,
},
entity, entity,
position: mining_queued.position, );
});
**mine_delay = 5; **mine_delay = 5;
} else if mining.is_none() } else if mining.is_none()
|| !is_same_mining_target( || !is_same_mining_target(
@ -304,14 +315,17 @@ fn handle_mining_queued(
) >= 1. ) >= 1.
{ {
// block was broken instantly // block was broken instantly
finish_mining_events.write(FinishMiningBlockEvent { commands.trigger_targets(
FinishMiningBlockEvent {
position: mining_queued.position,
},
entity, entity,
position: mining_queued.position, );
});
} else { } else {
let mining = Mining { let mining = Mining {
pos: mining_queued.position, pos: mining_queued.position,
dir: mining_queued.direction, dir: mining_queued.direction,
force: mining_queued.force,
}; };
trace!("inserting mining component {mining:?} for entity {entity:?}"); trace!("inserting mining component {mining:?} for entity {entity:?}");
commands.entity(entity).insert(mining); commands.entity(entity).insert(mining);
@ -370,7 +384,7 @@ fn is_same_mining_target(
} }
/// A component bundle for players that can mine blocks. /// A component bundle for players that can mine blocks.
#[derive(Bundle, Default)] #[derive(Bundle, Default, Clone)]
pub struct MineBundle { pub struct MineBundle {
pub delay: MineDelay, pub delay: MineDelay,
pub progress: MineProgress, pub progress: MineProgress,
@ -380,12 +394,12 @@ pub struct MineBundle {
} }
/// A component that counts down until we start mining the next block. /// 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); pub struct MineDelay(pub u32);
/// A component that stores the progress of the current mining operation. This /// A component that stores the progress of the current mining operation. This
/// is a value between 0 and 1. /// 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); pub struct MineProgress(pub f32);
impl MineProgress { impl MineProgress {
@ -413,15 +427,14 @@ pub struct MineBlockPos(pub Option<BlockPos>);
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)] #[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
pub struct MineItem(pub ItemStack); 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)] #[derive(Event)]
pub struct FinishMiningBlockEvent { pub struct FinishMiningBlockEvent {
pub entity: Entity,
pub position: BlockPos, pub position: BlockPos,
} }
pub fn handle_finish_mining_block_event( pub fn handle_finish_mining_block_observer(
mut events: EventReader<FinishMiningBlockEvent>, trigger: Trigger<FinishMiningBlockEvent>,
mut query: Query<( mut query: Query<(
&InstanceName, &InstanceName,
&LocalGameMode, &LocalGameMode,
@ -432,53 +445,48 @@ pub fn handle_finish_mining_block_event(
)>, )>,
instances: Res<InstanceContainer>, instances: Res<InstanceContainer>,
) { ) {
for event in events.read() { let event = trigger.event();
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;
}
if game_mode.current == GameMode::Creative { let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
let held_item = inventory.held_item().kind(); query.get_mut(trigger.target()).unwrap();
if matches!( let instance_lock = instances.get(instance_name).unwrap();
held_item, let instance = instance_lock.read();
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick if check_is_interaction_restricted(&instance, &event.position, &game_mode.current, inventory) {
) || azalea_registry::tags::items::SWORDS.contains(&held_item) return;
{
continue;
}
}
let Some(block_state) = instance.get_block_state(&event.position) else {
continue;
};
let registry_block = Box::<dyn Block>::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);
} }
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::<dyn Block>::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. /// Abort mining a block.
@ -535,7 +543,6 @@ pub fn continue_mining_block(
)>, )>,
mut commands: Commands, mut commands: Commands,
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>, mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
instances: Res<InstanceContainer>, instances: Res<InstanceContainer>,
) { ) {
for ( for (
@ -562,10 +569,12 @@ pub fn continue_mining_block(
if game_mode.current == GameMode::Creative { if game_mode.current == GameMode::Creative {
// TODO: worldborder check // TODO: worldborder check
**mine_delay = 5; **mine_delay = 5;
finish_mining_events.write(FinishMiningBlockEvent { commands.trigger_targets(
FinishMiningBlockEvent {
position: mining.pos,
},
entity, entity,
position: mining.pos, );
});
commands.trigger(SendPacketEvent::new( commands.trigger(SendPacketEvent::new(
entity, entity,
ServerboundPlayerAction { ServerboundPlayerAction {
@ -576,12 +585,14 @@ pub fn continue_mining_block(
}, },
)); ));
commands.trigger(SwingArmEvent { entity }); commands.trigger(SwingArmEvent { entity });
} else if is_same_mining_target( } else if mining.force
mining.pos, || is_same_mining_target(
inventory, mining.pos,
current_mining_pos, inventory,
current_mining_item, current_mining_pos,
) { current_mining_item,
)
{
trace!("continue mining block at {:?}", mining.pos); trace!("continue mining block at {:?}", mining.pos);
let instance_lock = instances.get(instance_name).unwrap(); let instance_lock = instances.get(instance_name).unwrap();
let instance = instance_lock.read(); let instance = instance_lock.read();
@ -612,10 +623,12 @@ pub fn continue_mining_block(
// repeatedly inserts MiningQueued // repeatedly inserts MiningQueued
commands.entity(entity).remove::<(Mining, MiningQueued)>(); commands.entity(entity).remove::<(Mining, MiningQueued)>();
trace!("finished mining block at {:?}", mining.pos); trace!("finished mining block at {:?}", mining.pos);
finish_mining_events.write(FinishMiningBlockEvent { commands.trigger_targets(
FinishMiningBlockEvent {
position: mining.pos,
},
entity, entity,
position: mining.pos, );
});
commands.trigger(SendPacketEvent::new( commands.trigger(SendPacketEvent::new(
entity, entity,
ServerboundPlayerAction { ServerboundPlayerAction {
@ -641,6 +654,7 @@ pub fn continue_mining_block(
commands.entity(entity).insert(MiningQueued { commands.entity(entity).insert(MiningQueued {
position: mining.pos, position: mining.pos,
direction: mining.dir, direction: mining.dir,
force: false,
}); });
} }
} }

View file

@ -13,11 +13,11 @@ use tracing::{debug, warn};
use super::as_system; use super::as_system;
use crate::{ use crate::{
InstanceHolder,
client::InConfigState, client::InConfigState,
connection::RawConnection, connection::RawConnection,
declare_packet_handlers, declare_packet_handlers,
disconnect::DisconnectEvent, disconnect::DisconnectEvent,
local_player::InstanceHolder,
packet::game::{KeepAliveEvent, ResourcePackEvent}, packet::game::{KeepAliveEvent, ResourcePackEvent},
}; };

View file

@ -12,7 +12,7 @@ use parking_lot::RwLock;
use tracing::{error, trace}; use tracing::{error, trace};
use uuid::Uuid; 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. /// An event that's sent when we receive a packet.
/// ``` /// ```

View file

@ -20,7 +20,7 @@ pub use events::*;
use tracing::{debug, error, trace, warn}; use tracing::{debug, error, trace, warn};
use crate::{ use crate::{
ClientInformation, PlayerInfo, ClientInformation,
chat::{ChatPacket, ChatReceivedEvent}, chat::{ChatPacket, ChatReceivedEvent},
chunks, chunks,
connection::RawConnection, connection::RawConnection,
@ -29,11 +29,10 @@ use crate::{
inventory::{ inventory::{
ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
}, },
local_player::{ local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList},
GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
},
movement::{KnockbackEvent, KnockbackType}, movement::{KnockbackEvent, KnockbackType},
packet::as_system, packet::as_system,
player::{GameProfileComponent, PlayerInfo},
}; };
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) { pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) {

View file

@ -17,8 +17,8 @@ use tracing::{debug, error};
use super::as_system; use super::as_system;
use crate::{ use crate::{
Account, GameProfileComponent, InConfigState, connection::RawConnection, Account, InConfigState, connection::RawConnection, declare_packet_handlers,
declare_packet_handlers, disconnect::DisconnectEvent, disconnect::DisconnectEvent, player::GameProfileComponent,
}; };
pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundLoginPacket) { pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundLoginPacket) {

View file

@ -30,8 +30,8 @@ use simdnbt::owned::{NbtCompound, NbtTag};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle, ClientInformation, InConfigState, LocalPlayerBundle, connection::RawConnection,
connection::RawConnection, disconnect::DisconnectEvent, disconnect::DisconnectEvent, local_player::InstanceHolder, player::GameProfileComponent,
}; };
/// A way to simulate a client in a server, used for some internal tests. /// A way to simulate a client in a server, used for some internal tests.

View file

@ -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_core::{position::ChunkPos, resource_location::ResourceLocation};
use azalea_entity::LocalEntity; use azalea_entity::LocalEntity;
use azalea_protocol::packets::{ use azalea_protocol::packets::{

View file

@ -4,6 +4,7 @@ use azalea::{
BlockPos, BlockPos,
pathfinder::{ pathfinder::{
astar::{self, PathfinderTimeout, WeightedNode, a_star}, astar::{self, PathfinderTimeout, WeightedNode, a_star},
custom_state::CustomPathfinderStateRef,
goals::{BlockPosGoal, Goal}, goals::{BlockPosGoal, Goal},
mining::MiningCache, mining::MiningCache,
rel_block_pos::RelBlockPos, rel_block_pos::RelBlockPos,
@ -41,13 +42,13 @@ fn generate_bedrock_world(
let mut chunk = chunk.write(); let mut chunk = chunk.write();
for x in 0..16_u8 { for x in 0..16_u8 {
for z in 0..16_u8 { for z in 0..16_u8 {
chunk.set( chunk.set_block_state(
&ChunkBlockPos::new(x, 1, z), &ChunkBlockPos::new(x, 1, z),
azalea_registry::Block::Bedrock.into(), azalea_registry::Block::Bedrock.into(),
chunks.min_y, chunks.min_y,
); );
if rng.gen_bool(0.5) { if rng.gen_bool(0.5) {
chunk.set( chunk.set_block_state(
&ChunkBlockPos::new(x, 2, z), &ChunkBlockPos::new(x, 2, z),
azalea_registry::Block::Bedrock.into(), azalea_registry::Block::Bedrock.into(),
chunks.min_y, 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 y in chunks.min_y..(chunks.min_y + chunks.height as i32) {
for x in 0..16_u8 { for x in 0..16_u8 {
for z in 0..16_u8 { for z in 0..16_u8 {
chunk.set( chunk.set_block_state(
&ChunkBlockPos::new(x, y, z), &ChunkBlockPos::new(x, y, z),
azalea_registry::Block::Stone.into(), azalea_registry::Block::Stone.into(),
chunks.min_y, chunks.min_y,
@ -134,7 +135,13 @@ fn run_pathfinder_benchmark(
let goal = BlockPosGoal(end); let goal = BlockPosGoal(end);
let successors = |pos: RelBlockPos| { 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 { let astar::Path {

View file

@ -25,7 +25,7 @@ fn generate_world(partial_chunks: &mut PartialChunkStorage, size: u32) -> ChunkS
let mut chunk = chunk.write(); let mut chunk = chunk.write();
for x in 0..16_u8 { for x in 0..16_u8 {
for z in 0..16_u8 { for z in 0..16_u8 {
chunk.set( chunk.set_block_state(
&ChunkBlockPos::new(x, 1, z), &ChunkBlockPos::new(x, 1, z),
azalea_registry::Block::OakFence.into(), azalea_registry::Block::OakFence.into(),
chunks.min_y, chunks.min_y,

View file

@ -3,8 +3,8 @@ pub mod debug;
pub mod movement; pub mod movement;
use azalea::{ use azalea::{
Client, GameProfileComponent, brigadier::prelude::*, chat::ChatPacket, ecs::prelude::*, Client, brigadier::prelude::*, chat::ChatPacket, ecs::prelude::*, entity::metadata::Player,
entity::metadata::Player, player::GameProfileComponent,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;

View file

@ -1,4 +1,4 @@
use azalea_client::{InstanceHolder, chat::SendChatEvent}; use azalea_client::{chat::SendChatEvent, local_player::InstanceHolder};
use azalea_core::position::Vec3; use azalea_core::position::Vec3;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;

View file

@ -27,9 +27,10 @@ use std::{
use astar::{Edge, PathfinderTimeout}; use astar::{Edge, PathfinderTimeout};
use azalea_client::{ use azalea_client::{
InstanceHolder, StartSprintEvent, StartWalkEvent, StartSprintEvent, StartWalkEvent,
inventory::{Inventory, InventorySet, SetSelectedHotbarSlotEvent}, inventory::{Inventory, InventorySet, SetSelectedHotbarSlotEvent},
mining::{Mining, StartMiningBlockEvent}, local_player::InstanceHolder,
mining::{Mining, MiningSet, StartMiningBlockEvent},
movement::MoveEventsSet, movement::MoveEventsSet,
}; };
use azalea_core::{position::BlockPos, tick::GameTick}; use azalea_core::{position::BlockPos, tick::GameTick};
@ -89,7 +90,8 @@ impl Plugin for PathfinderPlugin {
) )
.chain() .chain()
.after(PhysicsSet) .after(PhysicsSet)
.after(azalea_client::movement::send_position), .after(azalea_client::movement::send_position)
.after(MiningSet),
) )
.add_systems(PreUpdate, add_default_pathfinder) .add_systems(PreUpdate, add_default_pathfinder)
.add_systems( .add_systems(
@ -1271,6 +1273,7 @@ mod tests {
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use azalea_block::BlockState;
use azalea_core::position::{BlockPos, ChunkPos, Vec3}; use azalea_core::position::{BlockPos, ChunkPos, Vec3};
use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage};
@ -1286,9 +1289,9 @@ mod tests {
partial_chunks: &mut PartialChunkStorage, partial_chunks: &mut PartialChunkStorage,
start_pos: BlockPos, start_pos: BlockPos,
end_pos: BlockPos, end_pos: BlockPos,
solid_blocks: Vec<BlockPos>, solid_blocks: &[BlockPos],
) -> Simulation { ) -> 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 // you can uncomment this while debugging tests to get trace logs
// simulation.app.add_plugins(bevy_log::LogPlugin { // simulation.app.add_plugins(bevy_log::LogPlugin {
@ -1311,10 +1314,14 @@ mod tests {
fn setup_simulation_world( fn setup_simulation_world(
partial_chunks: &mut PartialChunkStorage, partial_chunks: &mut PartialChunkStorage,
start_pos: BlockPos, start_pos: BlockPos,
solid_blocks: Vec<BlockPos>, solid_blocks: &[BlockPos],
extra_blocks: &[(BlockPos, BlockState)],
) -> Simulation { ) -> Simulation {
let mut chunk_positions = HashSet::new(); 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)); chunk_positions.insert(ChunkPos::from(block_pos));
} }
@ -1323,8 +1330,12 @@ mod tests {
partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks);
} }
for block_pos in solid_blocks { 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( let player = SimulatedPlayerBundle::new(Vec3::new(
start_pos.x as f64 + 0.5, start_pos.x as f64 + 0.5,
start_pos.y as f64, start_pos.y as f64,
@ -1360,7 +1371,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(0, 71, 1), 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)); assert_simulation_reaches(&mut simulation, 20, BlockPos::new(0, 71, 1));
} }
@ -1372,7 +1383,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(2, 71, 2), BlockPos::new(2, 71, 2),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(1, 70, 1), BlockPos::new(1, 70, 1),
BlockPos::new(2, 70, 2), BlockPos::new(2, 70, 2),
@ -1390,7 +1401,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 3), BlockPos::new(0, 71, 3),
BlockPos::new(5, 76, 0), BlockPos::new(5, 76, 0),
vec![ &[
BlockPos::new(0, 70, 3), BlockPos::new(0, 70, 3),
BlockPos::new(0, 70, 2), BlockPos::new(0, 70, 2),
BlockPos::new(0, 70, 1), BlockPos::new(0, 70, 1),
@ -1412,7 +1423,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(0, 71, 3), 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)); assert_simulation_reaches(&mut simulation, 40, BlockPos::new(0, 71, 3));
} }
@ -1424,7 +1435,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(3, 67, 4), BlockPos::new(3, 67, 4),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 69, 1), BlockPos::new(0, 69, 1),
BlockPos::new(0, 68, 2), BlockPos::new(0, 68, 2),
@ -1443,7 +1454,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(0, 70, 5), BlockPos::new(0, 70, 5),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 70, 1), BlockPos::new(0, 70, 1),
BlockPos::new(0, 69, 2), BlockPos::new(0, 69, 2),
@ -1460,7 +1471,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(0, 68, 3), BlockPos::new(0, 68, 3),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 69, 1), BlockPos::new(0, 69, 1),
BlockPos::new(0, 68, 2), BlockPos::new(0, 68, 2),
@ -1477,7 +1488,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(3, 74, 0), BlockPos::new(3, 74, 0),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 71, 3), BlockPos::new(0, 71, 3),
BlockPos::new(3, 72, 3), BlockPos::new(3, 72, 3),
@ -1494,7 +1505,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(4, 71, 12), BlockPos::new(4, 71, 12),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 70, 4), BlockPos::new(0, 70, 4),
BlockPos::new(0, 70, 8), BlockPos::new(0, 70, 8),
@ -1512,7 +1523,7 @@ mod tests {
&mut partial_chunks, &mut partial_chunks,
BlockPos::new(0, 71, 0), BlockPos::new(0, 71, 0),
BlockPos::new(4, 74, 9), BlockPos::new(4, 74, 9),
vec![ &[
BlockPos::new(0, 70, 0), BlockPos::new(0, 70, 0),
BlockPos::new(0, 70, 1), BlockPos::new(0, 70, 1),
BlockPos::new(0, 70, 2), BlockPos::new(0, 70, 2),
@ -1526,4 +1537,41 @@ mod tests {
); );
assert_simulation_reaches(&mut simulation, 80, BlockPos::new(4, 74, 9)); 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));
}
} }

View file

@ -2,8 +2,13 @@
use std::sync::Arc; use std::sync::Arc;
use azalea_client::{PhysicsState, inventory::Inventory, packet::game::SendPacketEvent}; use azalea_client::{
use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; 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::{ use azalea_entity::{
Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance, Attributes, EntityDimensions, LookDirection, Physics, Position, attributes::AttributeInstance,
}; };
@ -87,7 +92,7 @@ fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc<RwLock<Instance
fn create_simulation_player_complete_bundle( fn create_simulation_player_complete_bundle(
instance: Arc<RwLock<Instance>>, instance: Arc<RwLock<Instance>>,
player: &SimulatedPlayerBundle, player: &SimulatedPlayerBundle,
) -> impl Bundle + use<> { ) -> impl Bundle {
let instance_name = simulation_instance_name(); let instance_name = simulation_instance_name();
( (
@ -100,12 +105,17 @@ fn create_simulation_player_complete_bundle(
azalea_registry::EntityKind::Player, azalea_registry::EntityKind::Player,
instance_name, instance_name,
), ),
azalea_client::InstanceHolder { azalea_client::local_player::InstanceHolder {
// partial_instance is never actually used by the pathfinder so // partial_instance is never actually used by the pathfinder so
partial_instance: Arc::new(RwLock::new(PartialInstance::default())), partial_instance: Arc::new(RwLock::new(PartialInstance::default())),
instance: instance.clone(), instance: instance.clone(),
}, },
Inventory::default(), Inventory::default(),
LocalGameMode::from(GameMode::Survival),
MineBundle::default(),
CurrentSequenceNumber::default(),
azalea_client::local_player::PermissionLevel::default(),
azalea_client::local_player::PlayerAbilities::default(),
) )
} }

View file

@ -1,4 +1,4 @@
use azalea_client::InstanceHolder; use azalea_client::local_player::InstanceHolder;
use azalea_world::MinecraftEntityId; use azalea_world::MinecraftEntityId;
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;