aboutsummaryrefslogtreecommitdiff
path: root/azalea-client
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-09-20 20:35:16 -1200
committermat <git@matdoes.dev>2025-09-20 20:35:16 -1200
commit585b51e91a5335eae37bc5af7c0111bb2092b156 (patch)
treec1559014df9db20dd625d9fe972d4e9f88317008 /azalea-client
parentdb793448ff8e656ad80859835edc3b89cb547dd2 (diff)
downloadazalea-drasl-585b51e91a5335eae37bc5af7c0111bb2092b156.tar.xz
more accurate mining and impl PartialEq for packets
Diffstat (limited to 'azalea-client')
-rw-r--r--azalea-client/Cargo.toml1
-rw-r--r--azalea-client/src/plugins/connection.rs19
-rw-r--r--azalea-client/src/plugins/inventory.rs12
-rw-r--r--azalea-client/src/plugins/mining.rs23
-rw-r--r--azalea-client/src/test_utils/simulation.rs13
-rw-r--r--azalea-client/tests/correct_sneak_movement.rs20
-rw-r--r--azalea-client/tests/mine_block_timing.rs127
7 files changed, 178 insertions, 37 deletions
diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml
index 1f91a669..5512fb60 100644
--- a/azalea-client/Cargo.toml
+++ b/azalea-client/Cargo.toml
@@ -27,6 +27,7 @@ bevy_tasks.workspace = true
bevy_time.workspace = true
chrono = { workspace = true, features = ["now"] }
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
+indexmap.workspace = true
minecraft_folder_path.workspace = true
parking_lot.workspace = true
pastey.workspace = true
diff --git a/azalea-client/src/plugins/connection.rs b/azalea-client/src/plugins/connection.rs
index a929a4c7..f439ac33 100644
--- a/azalea-client/src/plugins/connection.rs
+++ b/azalea-client/src/plugins/connection.rs
@@ -1,4 +1,12 @@
-use std::{fmt::Debug, io::Cursor, mem, sync::Arc};
+use std::{
+ fmt::Debug,
+ io::Cursor,
+ mem,
+ sync::{
+ Arc,
+ atomic::{self, AtomicBool},
+ },
+};
use azalea_crypto::Aes128CfbEnc;
use azalea_protocol::{
@@ -232,9 +240,12 @@ impl RawConnection {
if let Some(network) = &mut self.network {
network.write(packet)?;
} else {
- debug!(
- "tried to write packet to the network but there is no NetworkConnection. if you're trying to send a packet from the handler function, use self.write instead"
- );
+ static WARNED: AtomicBool = AtomicBool::new(false);
+ if !WARNED.swap(true, atomic::Ordering::Relaxed) {
+ debug!(
+ "tried to write packet to the network but there is no NetworkConnection. if you're trying to send a packet from the handler function, use self.write instead"
+ );
+ }
}
Ok(())
}
diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs
index ecc8e826..8037f8fc 100644
--- a/azalea-client/src/plugins/inventory.rs
+++ b/azalea-client/src/plugins/inventory.rs
@@ -1,7 +1,4 @@
-use std::{
- cmp,
- collections::{HashMap, HashSet},
-};
+use std::{cmp, collections::HashSet};
use azalea_chat::FormattedText;
use azalea_entity::PlayerAbilities;
@@ -22,6 +19,7 @@ use azalea_registry::MenuKind;
use azalea_world::{InstanceContainer, InstanceName};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
+use indexmap::IndexMap;
use tracing::{error, warn};
use crate::{Client, packet::game::SendPacketEvent, respawn::perform_respawn};
@@ -869,9 +867,9 @@ pub fn handle_container_click_event(
let registry_holder = &instance.read().registries;
- // see which slots changed after clicking and put them in the hashmap
- // the server uses this to check if we desynced
- let mut changed_slots: HashMap<u16, HashedStack> = HashMap::new();
+ // see which slots changed after clicking and put them in the map the server
+ // uses this to check if we desynced
+ let mut changed_slots: IndexMap<u16, HashedStack> = IndexMap::new();
for (slot_index, old_slot) in old_slots.iter().enumerate() {
let new_slot = &new_slots[slot_index];
if old_slot != new_slot {
diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs
index 1b7adadc..b6ac113a 100644
--- a/azalea-client/src/plugins/mining.rs
+++ b/azalea-client/src/plugins/mining.rs
@@ -8,7 +8,7 @@ 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::{debug, trace};
use crate::{
Client,
@@ -35,14 +35,14 @@ impl Plugin for MiningPlugin {
GameTick,
(
update_mining_component,
- continue_mining_block,
handle_auto_mine,
handle_mining_queued,
+ continue_mining_block,
)
.chain()
- .after(PhysicsSet)
- .after(super::movement::send_position)
- .after(super::attack::handle_attack_queued)
+ .before(PhysicsSet)
+ .before(super::movement::send_position)
+ .before(super::interact::handle_start_use_item_queued)
.in_set(MiningSet),
)
.add_systems(
@@ -358,9 +358,9 @@ fn handle_mining_queued(
seq: sequence_number.start_predicting(),
},
));
- // vanilla really does send two swing arm packets
- commands.trigger(SwingArmEvent { entity });
commands.trigger(SwingArmEvent { entity });
+ // another swing packet gets sent in the same tick in
+ // continue_mining_block, vanilla does this too
}
}
}
@@ -687,9 +687,18 @@ pub fn update_mining_component(
) {
for (entity, mut mining, hit_result_component) in &mut query.iter_mut() {
if let Some(block_hit_result) = hit_result_component.as_block_hit_result_if_not_miss() {
+ if mining.force && block_hit_result.block_pos != mining.pos {
+ continue;
+ }
+
mining.pos = block_hit_result.block_pos;
mining.dir = block_hit_result.direction;
} else {
+ if mining.force {
+ continue;
+ }
+
+ debug!("Removing mining component because we're no longer looking at the block");
commands.entity(entity).remove::<Mining>();
}
}
diff --git a/azalea-client/src/test_utils/simulation.rs b/azalea-client/src/test_utils/simulation.rs
index a763962b..aa3cef1b 100644
--- a/azalea-client/src/test_utils/simulation.rs
+++ b/azalea-client/src/test_utils/simulation.rs
@@ -232,9 +232,22 @@ impl SentPackets {
panic!("Expected {expected_formatted}, got nothing");
}
}
+
+ pub fn maybe_expect(&self, check: impl FnOnce(&ServerboundGamePacket) -> bool) {
+ let sent_packet = self.peek();
+ if let Some(sent_packet) = sent_packet
+ && check(&sent_packet)
+ {
+ self.next();
+ }
+ }
+
pub fn next(&self) -> Option<ServerboundGamePacket> {
self.list.lock().pop_front()
}
+ pub fn peek(&self) -> Option<ServerboundGamePacket> {
+ self.list.lock().front().cloned()
+ }
}
#[allow(clippy::type_complexity)]
diff --git a/azalea-client/tests/correct_sneak_movement.rs b/azalea-client/tests/correct_sneak_movement.rs
index cf1d17b5..9a9e8f4f 100644
--- a/azalea-client/tests/correct_sneak_movement.rs
+++ b/azalea-client/tests/correct_sneak_movement.rs
@@ -8,7 +8,6 @@ use azalea_protocol::{
common::movements::{PositionMoveRotation, RelativeMovements},
packets::{
ConnectionProtocol,
- config::{ClientboundFinishConfiguration, ClientboundRegistryData},
game::{
ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket,
ServerboundPlayerInput,
@@ -16,31 +15,14 @@ use azalea_protocol::{
},
};
use azalea_registry::{Block, DataRegistry, DimensionType};
-use simdnbt::owned::{NbtCompound, NbtTag};
#[test]
fn test_correct_sneak_movement() {
init_tracing();
- let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
+ let mut simulation = Simulation::new(ConnectionProtocol::Game);
let sent_packets = SentPackets::new(&mut simulation);
- simulation.receive_packet(ClientboundRegistryData {
- registry_id: ResourceLocation::new("minecraft:dimension_type"),
- entries: vec![(
- ResourceLocation::new("minecraft:overworld"),
- Some(NbtCompound::from_values(vec![
- ("height".into(), NbtTag::Int(384)),
- ("min_y".into(), NbtTag::Int(-64)),
- ])),
- )]
- .into_iter()
- .collect(),
- });
- simulation.tick();
- simulation.receive_packet(ClientboundFinishConfiguration);
- simulation.tick();
-
simulation.receive_packet(make_basic_login_packet(
DimensionType::new_raw(0), // overworld
ResourceLocation::new("minecraft:overworld"),
diff --git a/azalea-client/tests/mine_block_timing.rs b/azalea-client/tests/mine_block_timing.rs
new file mode 100644
index 00000000..1216f178
--- /dev/null
+++ b/azalea-client/tests/mine_block_timing.rs
@@ -0,0 +1,127 @@
+use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*};
+use azalea_core::{
+ direction::Direction,
+ position::{BlockPos, ChunkPos, Vec3},
+ resource_location::ResourceLocation,
+};
+use azalea_entity::LookDirection;
+use azalea_protocol::{
+ common::movements::{PositionMoveRotation, RelativeMovements},
+ packets::{
+ ConnectionProtocol, Packet,
+ game::{
+ ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket,
+ ServerboundPlayerAction, ServerboundSwing, s_interact::InteractionHand,
+ s_player_action,
+ },
+ },
+};
+use azalea_registry::{Block, DataRegistry, DimensionType};
+
+#[test]
+fn test_mine_block_timing() {
+ init_tracing();
+
+ let mut simulation = Simulation::new(ConnectionProtocol::Game);
+ let sent_packets = SentPackets::new(&mut simulation);
+ simulation.receive_packet(make_basic_login_packet(
+ DimensionType::new_raw(0),
+ ResourceLocation::new("azalea:overworld"),
+ ));
+
+ simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16));
+ simulation.tick();
+
+ let pos = BlockPos::new(0, 2, 0);
+ simulation.receive_packet(ClientboundBlockUpdate {
+ pos,
+ block_state: Block::Stone.into(),
+ });
+ simulation.receive_packet(ClientboundPlayerPosition {
+ id: 1,
+ change: PositionMoveRotation {
+ pos: pos.up(1).center_bottom(),
+ delta: Vec3::ZERO,
+ look_direction: LookDirection::default(),
+ },
+ relative: RelativeMovements::all_absolute(),
+ });
+ simulation.tick();
+ assert_eq!(simulation.get_block_state(pos), Some(Block::Stone.into()));
+ println!("set serverside stone");
+ simulation.with_component_mut::<LookDirection>(|look| {
+ // look down
+ look.update_x_rot(90.);
+ });
+
+ simulation.tick();
+ simulation.tick();
+ simulation.tick();
+
+ simulation.send_event(StartMiningBlockEvent {
+ entity: simulation.entity,
+ position: pos,
+ });
+ sent_packets.clear();
+ simulation.tick();
+ sent_packets.expect("ServerboundPlayerAction", |p| {
+ p == &ServerboundGamePacket::PlayerAction(ServerboundPlayerAction {
+ action: s_player_action::Action::StartDestroyBlock,
+ pos,
+ direction: Direction::Up,
+ seq: 1,
+ })
+ });
+ sent_packets.expect("Swing 1", |p| {
+ p == &ServerboundGamePacket::Swing(ServerboundSwing {
+ hand: InteractionHand::MainHand,
+ })
+ .into_variant()
+ });
+ sent_packets.expect("Swing 2", |p| {
+ p == &ServerboundGamePacket::Swing(ServerboundSwing {
+ hand: InteractionHand::MainHand,
+ })
+ .into_variant()
+ });
+ sent_packets.expect_tick_end();
+ sent_packets.expect_empty();
+
+ for i in 3..=151 {
+ simulation.tick();
+ sent_packets.expect(&format!("Swing {i}"), |p| {
+ p == &ServerboundGamePacket::Swing(ServerboundSwing {
+ hand: InteractionHand::MainHand,
+ })
+ .into_variant()
+ });
+ sent_packets.maybe_expect(|p| matches!(p, ServerboundGamePacket::MovePlayerPos(_)));
+ sent_packets.expect_tick_end();
+ sent_packets.expect_empty();
+ }
+
+ simulation.tick();
+ sent_packets.expect(
+ "ServerboundPlayerAction { action: StopDestroyBlock }",
+ |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::PlayerAction(p)
+ if p.action == s_player_action::Action::StopDestroyBlock
+ )
+ },
+ );
+ sent_packets.expect("Last swing", |p| {
+ p == &ServerboundGamePacket::Swing(ServerboundSwing {
+ hand: InteractionHand::MainHand,
+ })
+ .into_variant()
+ });
+
+ for _ in 0..3 {
+ sent_packets.maybe_expect(|p| matches!(p, ServerboundGamePacket::MovePlayerPos(_)));
+ sent_packets.expect_tick_end();
+ sent_packets.expect_empty();
+ simulation.tick();
+ }
+}