aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/mining.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-02-22 21:45:26 -0600
committerGitHub <noreply@github.com>2025-02-22 21:45:26 -0600
commite21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7 (patch)
treeadd6f8bfce40d0c07845d8aa4c9945a0b918444c /azalea-client/src/mining.rs
parentf8130c3c92946d2293634ba4e252d6bc93026c3c (diff)
downloadazalea-drasl-e21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7.tar.xz
Refactor azalea-client (#205)
* start organizing packet_handling more by moving packet handlers into their own functions * finish writing all the handler functions for packets * use macro for generating match statement for packet handler functions * fix set_entity_data * update config state to also use handler functions * organize az-client file structure by moving things into plugins directory * fix merge issues
Diffstat (limited to 'azalea-client/src/mining.rs')
-rw-r--r--azalea-client/src/mining.rs643
1 files changed, 0 insertions, 643 deletions
diff --git a/azalea-client/src/mining.rs b/azalea-client/src/mining.rs
deleted file mode 100644
index 03063b3e..00000000
--- a/azalea-client/src/mining.rs
+++ /dev/null
@@ -1,643 +0,0 @@
-use azalea_block::{Block, BlockState, fluid_state::FluidState};
-use azalea_core::{direction::Direction, game_type::GameMode, position::BlockPos, tick::GameTick};
-use azalea_entity::{FluidOnEyes, Physics, mining::get_mine_progress};
-use azalea_inventory::ItemStack;
-use azalea_physics::PhysicsSet;
-use azalea_protocol::packets::game::s_player_action::{self, ServerboundPlayerAction};
-use azalea_world::{InstanceContainer, InstanceName};
-use bevy_app::{App, Plugin, Update};
-use bevy_ecs::prelude::*;
-use derive_more::{Deref, DerefMut};
-
-use crate::{
- Client,
- interact::{
- CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
- check_is_interaction_restricted,
- },
- inventory::{Inventory, InventorySet},
- local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
- movement::MoveEventsSet,
- packet_handling::game::SendPacketEvent,
-};
-
-/// A plugin that allows clients to break blocks in the world.
-pub struct MinePlugin;
-impl Plugin for MinePlugin {
- fn build(&self, app: &mut App) {
- app.add_event::<StartMiningBlockEvent>()
- .add_event::<StartMiningBlockWithDirectionEvent>()
- .add_event::<FinishMiningBlockEvent>()
- .add_event::<StopMiningBlockEvent>()
- .add_event::<MineBlockProgressEvent>()
- .add_event::<AttackBlockEvent>()
- .add_systems(
- GameTick,
- (continue_mining_block, handle_auto_mine)
- .chain()
- .before(PhysicsSet),
- )
- .add_systems(
- Update,
- (
- handle_start_mining_block_event,
- handle_start_mining_block_with_direction_event,
- handle_finish_mining_block_event,
- handle_stop_mining_block_event,
- )
- .chain()
- .in_set(MiningSet)
- .after(InventorySet)
- .after(MoveEventsSet)
- .before(azalea_entity::update_bounding_box)
- .after(azalea_entity::update_fluid_on_eyes)
- .after(crate::interact::update_hit_result_component)
- .after(crate::attack::handle_attack_event)
- .after(crate::interact::handle_block_interact_event)
- .before(crate::interact::handle_swing_arm_event),
- );
- }
-}
-
-#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
-pub struct MiningSet;
-
-impl Client {
- pub fn start_mining(&mut self, position: BlockPos) {
- self.ecs.lock().send_event(StartMiningBlockEvent {
- entity: self.entity,
- position,
- });
- }
-
- /// When enabled, the bot will mine any block that it is looking at if it is
- /// reachable.
- pub fn left_click_mine(&self, enabled: bool) {
- let mut ecs = self.ecs.lock();
- let mut entity_mut = ecs.entity_mut(self.entity);
-
- if enabled {
- entity_mut.insert(LeftClickMine);
- } else {
- entity_mut.remove::<LeftClickMine>();
- }
- }
-}
-
-/// A component that simulates the client holding down left click to mine the
-/// block that it's facing, but this only interacts with blocks and not
-/// entities.
-#[derive(Component)]
-pub struct LeftClickMine;
-
-#[allow(clippy::type_complexity)]
-fn handle_auto_mine(
- mut query: Query<
- (
- &HitResultComponent,
- Entity,
- Option<&Mining>,
- &Inventory,
- &MineBlockPos,
- &MineItem,
- ),
- With<LeftClickMine>,
- >,
- mut start_mining_block_event: EventWriter<StartMiningBlockEvent>,
- mut stop_mining_block_event: EventWriter<StopMiningBlockEvent>,
-) {
- for (
- hit_result_component,
- entity,
- mining,
- inventory,
- current_mining_pos,
- current_mining_item,
- ) in &mut query.iter_mut()
- {
- let block_pos = hit_result_component.block_pos;
-
- if (mining.is_none()
- || !is_same_mining_target(
- block_pos,
- inventory,
- current_mining_pos,
- current_mining_item,
- ))
- && !hit_result_component.miss
- {
- start_mining_block_event.send(StartMiningBlockEvent {
- entity,
- position: block_pos,
- });
- } else if mining.is_some() && hit_result_component.miss {
- stop_mining_block_event.send(StopMiningBlockEvent { entity });
- }
- }
-}
-
-/// Information about the block we're currently mining. This is only present if
-/// we're currently mining a block.
-#[derive(Component)]
-pub struct Mining {
- pub pos: BlockPos,
- pub dir: Direction,
-}
-
-/// 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)]
-pub struct StartMiningBlockEvent {
- pub entity: Entity,
- pub position: BlockPos,
-}
-fn handle_start_mining_block_event(
- mut events: EventReader<StartMiningBlockEvent>,
- mut start_mining_events: EventWriter<StartMiningBlockWithDirectionEvent>,
- mut query: Query<&HitResultComponent>,
-) {
- for event in events.read() {
- let hit_result = query.get_mut(event.entity).unwrap();
- let direction = if hit_result.block_pos == event.position {
- // we're looking at the block
- hit_result.direction
- } else {
- // we're not looking at the block, arbitrary direction
- Direction::Down
- };
- start_mining_events.send(StartMiningBlockWithDirectionEvent {
- entity: event.entity,
- position: event.position,
- direction,
- });
- }
-}
-
-#[derive(Event)]
-pub struct StartMiningBlockWithDirectionEvent {
- pub entity: Entity,
- pub position: BlockPos,
- pub direction: Direction,
-}
-#[allow(clippy::too_many_arguments, clippy::type_complexity)]
-fn handle_start_mining_block_with_direction_event(
- mut events: EventReader<StartMiningBlockWithDirectionEvent>,
- mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
- mut attack_block_events: EventWriter<AttackBlockEvent>,
- mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
- mut query: Query<(
- &InstanceName,
- &LocalGameMode,
- &Inventory,
- &FluidOnEyes,
- &Physics,
- Option<&Mining>,
- &mut CurrentSequenceNumber,
- &mut MineDelay,
- &mut MineProgress,
- &mut MineTicks,
- &mut MineItem,
- &mut MineBlockPos,
- )>,
- instances: Res<InstanceContainer>,
- mut commands: Commands,
-) {
- for event in events.read() {
- let (
- instance_name,
- game_mode,
- inventory,
- fluid_on_eyes,
- physics,
- mining,
- mut sequence_number,
- mut mine_delay,
- mut mine_progress,
- mut mine_ticks,
- mut current_mining_item,
- mut current_mining_pos,
- ) = 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;
- }
- // TODO (when world border is implemented): vanilla ignores if the block
- // is outside of the worldborder
-
- if game_mode.current == GameMode::Creative {
- *sequence_number += 1;
- finish_mining_events.send(FinishMiningBlockEvent {
- entity: event.entity,
- position: event.position,
- });
- **mine_delay = 5;
- } else if mining.is_none()
- || !is_same_mining_target(
- event.position,
- inventory,
- &current_mining_pos,
- &current_mining_item,
- )
- {
- if mining.is_some() {
- // send a packet to stop mining since we just changed target
- send_packet_events.send(SendPacketEvent::new(
- event.entity,
- ServerboundPlayerAction {
- action: s_player_action::Action::AbortDestroyBlock,
- pos: current_mining_pos
- .expect("IsMining is true so MineBlockPos must be present"),
- direction: event.direction,
- sequence: 0,
- },
- ));
- }
-
- let target_block_state = instance
- .get_block_state(&event.position)
- .unwrap_or_default();
- *sequence_number += 1;
- let target_registry_block = azalea_registry::Block::from(target_block_state);
-
- // we can't break blocks if they don't have a bounding box
-
- // TODO: So right now azalea doesn't differenciate between different types of
- // bounding boxes. See ClipContext::block_shape for more info. Ideally this
- // should just call ClipContext::block_shape and check if it's empty.
- let block_is_solid = !target_block_state.is_air()
- // this is a hack to make sure we can't break water or lava
- && !matches!(
- target_registry_block,
- azalea_registry::Block::Water | azalea_registry::Block::Lava
- );
-
- if block_is_solid && **mine_progress == 0. {
- // interact with the block (like note block left click) here
- attack_block_events.send(AttackBlockEvent {
- entity: event.entity,
- position: event.position,
- });
- }
-
- let block = Box::<dyn Block>::from(target_block_state);
-
- let held_item = inventory.held_item();
-
- if block_is_solid
- && get_mine_progress(
- block.as_ref(),
- held_item.kind(),
- &inventory.inventory_menu,
- fluid_on_eyes,
- physics,
- ) >= 1.
- {
- // block was broken instantly
- finish_mining_events.send(FinishMiningBlockEvent {
- entity: event.entity,
- position: event.position,
- });
- } else {
- commands.entity(event.entity).insert(Mining {
- pos: event.position,
- dir: event.direction,
- });
- **current_mining_pos = Some(event.position);
- **current_mining_item = held_item;
- **mine_progress = 0.;
- **mine_ticks = 0.;
- mine_block_progress_events.send(MineBlockProgressEvent {
- entity: event.entity,
- position: event.position,
- destroy_stage: mine_progress.destroy_stage(),
- });
- }
-
- send_packet_events.send(SendPacketEvent::new(
- event.entity,
- ServerboundPlayerAction {
- action: s_player_action::Action::StartDestroyBlock,
- pos: event.position,
- direction: event.direction,
- sequence: **sequence_number,
- },
- ));
- }
- }
-}
-
-#[derive(Event)]
-pub struct MineBlockProgressEvent {
- pub entity: Entity,
- pub position: BlockPos,
- pub destroy_stage: Option<u32>,
-}
-
-/// A player left clicked on a block, used for stuff like interacting with note
-/// blocks.
-#[derive(Event)]
-pub struct AttackBlockEvent {
- pub entity: Entity,
- pub position: BlockPos,
-}
-
-/// Returns whether the block and item are still the same as when we started
-/// mining.
-fn is_same_mining_target(
- target_block: BlockPos,
- inventory: &Inventory,
- current_mining_pos: &MineBlockPos,
- current_mining_item: &MineItem,
-) -> bool {
- let held_item = inventory.held_item();
- Some(target_block) == current_mining_pos.0 && held_item == current_mining_item.0
-}
-
-/// A component bundle for players that can mine blocks.
-#[derive(Bundle, Default)]
-pub struct MineBundle {
- pub delay: MineDelay,
- pub progress: MineProgress,
- pub ticks: MineTicks,
- pub mining_pos: MineBlockPos,
- pub mine_item: MineItem,
-}
-
-/// A component that counts down until we start mining the next block.
-#[derive(Component, Debug, Default, Deref, DerefMut)]
-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)]
-pub struct MineProgress(pub f32);
-
-impl MineProgress {
- pub fn destroy_stage(&self) -> Option<u32> {
- if self.0 > 0. {
- Some((self.0 * 10.) as u32)
- } else {
- None
- }
- }
-}
-
-/// A component that stores the number of ticks that we've been mining the same
-/// block for. This is a float even though it should only ever be a round
-/// number.
-#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
-pub struct MineTicks(pub f32);
-
-/// A component that stores the position of the block we're currently mining.
-#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
-pub struct MineBlockPos(pub Option<BlockPos>);
-
-/// A component that contains the item we're currently using to mine. If we're
-/// not mining anything, it'll be [`ItemStack::Empty`].
-#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
-pub struct MineItem(pub ItemStack);
-
-/// 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<FinishMiningBlockEvent>,
- mut query: Query<(
- &InstanceName,
- &LocalGameMode,
- &Inventory,
- &PlayerAbilities,
- &PermissionLevel,
- &mut CurrentSequenceNumber,
- )>,
- instances: Res<InstanceContainer>,
-) {
- 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;
- }
-
- 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::<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);
- }
-}
-
-/// Abort mining a block.
-#[derive(Event)]
-pub struct StopMiningBlockEvent {
- pub entity: Entity,
-}
-pub fn handle_stop_mining_block_event(
- mut events: EventReader<StopMiningBlockEvent>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
- mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
- mut query: Query<(&mut Mining, &MineBlockPos, &mut MineProgress)>,
- mut commands: Commands,
-) {
- for event in events.read() {
- let (mut _mining, mine_block_pos, mut mine_progress) = query.get_mut(event.entity).unwrap();
-
- let mine_block_pos =
- mine_block_pos.expect("IsMining is true so MineBlockPos must be present");
- send_packet_events.send(SendPacketEvent::new(
- event.entity,
- ServerboundPlayerAction {
- action: s_player_action::Action::AbortDestroyBlock,
- pos: mine_block_pos,
- direction: Direction::Down,
- sequence: 0,
- },
- ));
- commands.entity(event.entity).remove::<Mining>();
- **mine_progress = 0.;
- mine_block_progress_events.send(MineBlockProgressEvent {
- entity: event.entity,
- position: mine_block_pos,
- destroy_stage: None,
- });
- }
-}
-
-#[allow(clippy::too_many_arguments, clippy::type_complexity)]
-pub fn continue_mining_block(
- mut query: Query<(
- Entity,
- &InstanceName,
- &LocalGameMode,
- &Inventory,
- &MineBlockPos,
- &MineItem,
- &FluidOnEyes,
- &Physics,
- &Mining,
- &mut MineDelay,
- &mut MineProgress,
- &mut MineTicks,
- &mut CurrentSequenceNumber,
- )>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
- mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
- mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
- mut start_mining_events: EventWriter<StartMiningBlockWithDirectionEvent>,
- mut swing_arm_events: EventWriter<SwingArmEvent>,
- instances: Res<InstanceContainer>,
- mut commands: Commands,
-) {
- for (
- entity,
- instance_name,
- game_mode,
- inventory,
- current_mining_pos,
- current_mining_item,
- fluid_on_eyes,
- physics,
- mining,
- mut mine_delay,
- mut mine_progress,
- mut mine_ticks,
- mut sequence_number,
- ) in query.iter_mut()
- {
- if **mine_delay > 0 {
- **mine_delay -= 1;
- continue;
- }
-
- if game_mode.current == GameMode::Creative {
- // TODO: worldborder check
- **mine_delay = 5;
- finish_mining_events.send(FinishMiningBlockEvent {
- entity,
- position: mining.pos,
- });
- *sequence_number += 1;
- send_packet_events.send(SendPacketEvent::new(
- entity,
- ServerboundPlayerAction {
- action: s_player_action::Action::StartDestroyBlock,
- pos: mining.pos,
- direction: mining.dir,
- sequence: **sequence_number,
- },
- ));
- swing_arm_events.send(SwingArmEvent { entity });
- } else if is_same_mining_target(
- mining.pos,
- inventory,
- current_mining_pos,
- current_mining_item,
- ) {
- let instance_lock = instances.get(instance_name).unwrap();
- let instance = instance_lock.read();
- let target_block_state = instance.get_block_state(&mining.pos).unwrap_or_default();
-
- if target_block_state.is_air() {
- commands.entity(entity).remove::<Mining>();
- continue;
- }
- let block = Box::<dyn Block>::from(target_block_state);
- **mine_progress += get_mine_progress(
- block.as_ref(),
- current_mining_item.kind(),
- &inventory.inventory_menu,
- fluid_on_eyes,
- physics,
- );
-
- if **mine_ticks % 4. == 0. {
- // vanilla makes a mining sound here
- }
- **mine_ticks += 1.;
-
- if **mine_progress >= 1. {
- commands.entity(entity).remove::<Mining>();
- *sequence_number += 1;
- finish_mining_events.send(FinishMiningBlockEvent {
- entity,
- position: mining.pos,
- });
- send_packet_events.send(SendPacketEvent::new(
- entity,
- ServerboundPlayerAction {
- action: s_player_action::Action::StopDestroyBlock,
- pos: mining.pos,
- direction: mining.dir,
- sequence: **sequence_number,
- },
- ));
- **mine_progress = 0.;
- **mine_ticks = 0.;
- **mine_delay = 0;
- }
-
- mine_block_progress_events.send(MineBlockProgressEvent {
- entity,
- position: mining.pos,
- destroy_stage: mine_progress.destroy_stage(),
- });
- swing_arm_events.send(SwingArmEvent { entity });
- } else {
- start_mining_events.send(StartMiningBlockWithDirectionEvent {
- entity,
- position: mining.pos,
- direction: mining.dir,
- });
- }
-
- swing_arm_events.send(SwingArmEvent { entity });
- }
-}