From 82e3d46ca319badcbc584cf902aeebcbd30948b9 Mon Sep 17 00:00:00 2001 From: mat Date: Mon, 22 Dec 2025 21:43:54 -1400 Subject: run azalea-client integration tests as one binary per https://corrode.dev/blog/tips-for-faster-rust-compile-times/\#combine-all-integration-tests-into-a-single-binary <3 --- azalea-client/build.rs | 43 ++++++ azalea-client/src/test_utils/simulation.rs | 16 ++- azalea-client/src/test_utils/tracing.rs | 70 +++++++-- .../tests/change_dimension_to_nether_and_back.rs | 154 -------------------- azalea-client/tests/client_disconnect.rs | 20 --- azalea-client/tests/close_open_container.rs | 67 --------- azalea-client/tests/correct_movement.rs | 79 ----------- azalea-client/tests/correct_sneak_movement.rs | 111 --------------- .../tests/correct_sprint_sneak_movement.rs | 129 ----------------- .../despawn_entities_when_changing_dimension.rs | 81 ----------- azalea-client/tests/enchantments.rs | 124 ---------------- azalea-client/tests/fast_login.rs | 42 ------ .../tests/login_to_dimension_with_same_name.rs | 130 ----------------- azalea-client/tests/main.rs | 1 + azalea-client/tests/mine_block_rollback.rs | 44 ------ azalea-client/tests/mine_block_timing_hand.rs | 155 -------------------- azalea-client/tests/mine_block_without_rollback.rs | 46 ------ azalea-client/tests/move_and_despawn_entity.rs | 40 ------ azalea-client/tests/move_despawned_entity.rs | 45 ------ azalea-client/tests/packet_order.rs | 128 ----------------- .../tests/packet_order_set_carried_item.rs | 110 -------------- ...receive_spawn_entity_and_start_config_packet.rs | 36 ----- azalea-client/tests/receive_start_config_packet.rs | 20 --- azalea-client/tests/reply_to_ping_with_pong.rs | 78 ---------- azalea-client/tests/set_health_before_login.rs | 50 ------- .../change_dimension_to_nether_and_back.rs | 154 ++++++++++++++++++++ .../tests/simulation/client_disconnect.rs | 20 +++ .../tests/simulation/close_open_container.rs | 67 +++++++++ azalea-client/tests/simulation/correct_movement.rs | 79 +++++++++++ .../tests/simulation/correct_sneak_movement.rs | 111 +++++++++++++++ .../simulation/correct_sprint_sneak_movement.rs | 129 +++++++++++++++++ .../despawn_entities_when_changing_dimension.rs | 81 +++++++++++ azalea-client/tests/simulation/enchantments.rs | 124 ++++++++++++++++ azalea-client/tests/simulation/fast_login.rs | 42 ++++++ .../login_to_dimension_with_same_name.rs | 130 +++++++++++++++++ .../tests/simulation/mine_block_rollback.rs | 44 ++++++ .../tests/simulation/mine_block_timing_hand.rs | 155 ++++++++++++++++++++ .../simulation/mine_block_without_rollback.rs | 46 ++++++ azalea-client/tests/simulation/mod.rs | 25 ++++ .../tests/simulation/move_and_despawn_entity.rs | 40 ++++++ .../tests/simulation/move_despawned_entity.rs | 45 ++++++ azalea-client/tests/simulation/packet_order.rs | 128 +++++++++++++++++ .../simulation/packet_order_set_carried_item.rs | 110 ++++++++++++++ ...receive_spawn_entity_and_start_config_packet.rs | 36 +++++ .../simulation/receive_start_config_packet.rs | 20 +++ .../tests/simulation/reply_to_ping_with_pong.rs | 78 ++++++++++ .../tests/simulation/set_health_before_login.rs | 50 +++++++ .../tests/simulation/teleport_movement.rs | 158 +++++++++++++++++++++ azalea-client/tests/simulation/ticks_alive.rs | 32 +++++ azalea-client/tests/teleport_movement.rs | 158 --------------------- azalea-client/tests/ticks_alive.rs | 32 ----- 51 files changed, 2013 insertions(+), 1900 deletions(-) create mode 100644 azalea-client/build.rs delete mode 100644 azalea-client/tests/change_dimension_to_nether_and_back.rs delete mode 100644 azalea-client/tests/client_disconnect.rs delete mode 100644 azalea-client/tests/close_open_container.rs delete mode 100644 azalea-client/tests/correct_movement.rs delete mode 100644 azalea-client/tests/correct_sneak_movement.rs delete mode 100644 azalea-client/tests/correct_sprint_sneak_movement.rs delete mode 100644 azalea-client/tests/despawn_entities_when_changing_dimension.rs delete mode 100644 azalea-client/tests/enchantments.rs delete mode 100644 azalea-client/tests/fast_login.rs delete mode 100644 azalea-client/tests/login_to_dimension_with_same_name.rs create mode 100644 azalea-client/tests/main.rs delete mode 100644 azalea-client/tests/mine_block_rollback.rs delete mode 100644 azalea-client/tests/mine_block_timing_hand.rs delete mode 100644 azalea-client/tests/mine_block_without_rollback.rs delete mode 100644 azalea-client/tests/move_and_despawn_entity.rs delete mode 100644 azalea-client/tests/move_despawned_entity.rs delete mode 100644 azalea-client/tests/packet_order.rs delete mode 100644 azalea-client/tests/packet_order_set_carried_item.rs delete mode 100644 azalea-client/tests/receive_spawn_entity_and_start_config_packet.rs delete mode 100644 azalea-client/tests/receive_start_config_packet.rs delete mode 100644 azalea-client/tests/reply_to_ping_with_pong.rs delete mode 100644 azalea-client/tests/set_health_before_login.rs create mode 100644 azalea-client/tests/simulation/change_dimension_to_nether_and_back.rs create mode 100644 azalea-client/tests/simulation/client_disconnect.rs create mode 100644 azalea-client/tests/simulation/close_open_container.rs create mode 100644 azalea-client/tests/simulation/correct_movement.rs create mode 100644 azalea-client/tests/simulation/correct_sneak_movement.rs create mode 100644 azalea-client/tests/simulation/correct_sprint_sneak_movement.rs create mode 100644 azalea-client/tests/simulation/despawn_entities_when_changing_dimension.rs create mode 100644 azalea-client/tests/simulation/enchantments.rs create mode 100644 azalea-client/tests/simulation/fast_login.rs create mode 100644 azalea-client/tests/simulation/login_to_dimension_with_same_name.rs create mode 100644 azalea-client/tests/simulation/mine_block_rollback.rs create mode 100644 azalea-client/tests/simulation/mine_block_timing_hand.rs create mode 100644 azalea-client/tests/simulation/mine_block_without_rollback.rs create mode 100644 azalea-client/tests/simulation/mod.rs create mode 100644 azalea-client/tests/simulation/move_and_despawn_entity.rs create mode 100644 azalea-client/tests/simulation/move_despawned_entity.rs create mode 100644 azalea-client/tests/simulation/packet_order.rs create mode 100644 azalea-client/tests/simulation/packet_order_set_carried_item.rs create mode 100644 azalea-client/tests/simulation/receive_spawn_entity_and_start_config_packet.rs create mode 100644 azalea-client/tests/simulation/receive_start_config_packet.rs create mode 100644 azalea-client/tests/simulation/reply_to_ping_with_pong.rs create mode 100644 azalea-client/tests/simulation/set_health_before_login.rs create mode 100644 azalea-client/tests/simulation/teleport_movement.rs create mode 100644 azalea-client/tests/simulation/ticks_alive.rs delete mode 100644 azalea-client/tests/teleport_movement.rs delete mode 100644 azalea-client/tests/ticks_alive.rs diff --git a/azalea-client/build.rs b/azalea-client/build.rs new file mode 100644 index 00000000..49ef62db --- /dev/null +++ b/azalea-client/build.rs @@ -0,0 +1,43 @@ +use std::{ + fs::{self, File}, + io::Write, +}; + +fn main() { + // Tell Cargo that if the given file changes, to rerun this build script. + println!("cargo::rerun-if-changed=tests"); + + let Ok(paths) = fs::read_dir("tests/simulation") else { + return; + }; + + let mut modules = Vec::new(); + + for path in paths { + let Ok(path) = path else { + continue; + }; + let path = path.path(); + if path.extension().is_none_or(|ext| ext != "rs") { + continue; + } + let mod_name = path.with_extension(""); + let mod_name = mod_name.file_name().unwrap().to_string_lossy().to_string(); + if mod_name == "mod" { + continue; + } + modules.push(mod_name); + } + + let Ok(mut mod_rs) = File::create("tests/simulation/mod.rs") else { + return; + }; + let _ = writeln!( + mod_rs, + "// This file is @generated by `azalea-client/build.rs`.\n" + ); + modules.sort(); + for mod_name in modules { + let _ = writeln!(mod_rs, "mod {mod_name};"); + } +} diff --git a/azalea-client/src/test_utils/simulation.rs b/azalea-client/src/test_utils/simulation.rs index 5a1c9c52..a6bc22ab 100644 --- a/azalea-client/src/test_utils/simulation.rs +++ b/azalea-client/src/test_utils/simulation.rs @@ -57,8 +57,9 @@ pub struct Simulation { } impl Simulation { - pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self { + pub fn new(conn_protocol: ConnectionProtocol) -> Self { let mut app = create_simulation_app(); + let mut entity = app.world_mut().spawn_empty(); let (player, rt) = create_local_player_bundle(entity.id(), ConnectionProtocol::Configuration); @@ -78,7 +79,7 @@ impl Simulation { let mut simulation = Self { app, entity, rt }; #[allow(clippy::single_match)] - match initial_connection_protocol { + match conn_protocol { ConnectionProtocol::Configuration => {} ConnectionProtocol::Game => { simulation.receive_packet(ClientboundRegistryData { @@ -97,7 +98,7 @@ impl Simulation { simulation.receive_packet(ClientboundFinishConfiguration); simulation.tick(); } - _ => unimplemented!("unsupported ConnectionProtocol {initial_connection_protocol:?}"), + _ => unimplemented!("unsupported ConnectionProtocol {conn_protocol:?}"), } simulation @@ -300,10 +301,13 @@ fn create_local_player_bundle( fn create_simulation_app() -> App { let mut app = App::new(); + let mut plugins = bevy_app::PluginGroup::build(crate::DefaultPlugins); #[cfg(feature = "log")] - app.add_plugins( - bevy_app::PluginGroup::build(crate::DefaultPlugins).disable::(), - ); + { + plugins = plugins.disable::(); + } + + app.add_plugins(plugins); app.edit_schedule(bevy_app::Main, |schedule| { // makes test results more reproducible diff --git a/azalea-client/src/test_utils/tracing.rs b/azalea-client/src/test_utils/tracing.rs index 207a4625..3be92260 100644 --- a/azalea-client/src/test_utils/tracing.rs +++ b/azalea-client/src/test_utils/tracing.rs @@ -1,27 +1,67 @@ +use std::sync::LazyLock; + use bevy_log::tracing_subscriber::{ - self, EnvFilter, Layer, + self, EnvFilter, Layer, Registry, + filter::Filtered, + fmt, layer::{Context, SubscriberExt}, + reload::{self, Handle}, util::SubscriberInitExt, }; +use parking_lot::{Mutex, MutexGuard}; use tracing::{Event, Level, Subscriber}; -pub fn init_tracing() { - init_tracing_with_level(Level::WARN); +static LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +pub fn init<'a>() -> MutexGuard<'a, ()> { + init_with_level(Level::WARN) +} + +// can't treat these as one layer due to https://github.com/tokio-rs/tracing/issues/1629 +struct LayerReloadHandles { + filter: Handle, EnvFilter, Registry>, Registry>, + test: Handle, } -pub fn init_tracing_with_level(max_level: Level) { +static RELOAD_HANDLES: LazyLock = LazyLock::new(|| { + let (filter_layer, filter_reload_handle) = reload::Layer::new( + fmt::layer().with_filter( + EnvFilter::builder() + .with_default_directive(Level::WARN.into()) + .from_env_lossy(), + ), + ); + let (test_layer, test_reload_handle) = reload::Layer::new(TestTracingLayer { + panic_on_level: Level::WARN, + }); + tracing_subscriber::registry() - .with( - tracing_subscriber::fmt::layer().with_filter( - EnvFilter::builder() - .with_default_directive(max_level.into()) - .from_env_lossy(), - ), - ) - .with(TestTracingLayer { - panic_on_level: max_level, - }) + .with(filter_layer.and_then(test_layer)) .init(); + + LayerReloadHandles { + filter: filter_reload_handle, + test: test_reload_handle, + } +}); + +pub fn init_with_level<'a>(max_level: Level) -> MutexGuard<'a, ()> { + let lock = LOCK.lock(); + + RELOAD_HANDLES + .filter + .modify(|layer| { + *layer.filter_mut() = EnvFilter::builder() + .with_default_directive(max_level.into()) + .from_env_lossy() + }) + .unwrap(); + RELOAD_HANDLES + .test + .modify(|layer| layer.panic_on_level = max_level) + .unwrap(); + + lock } struct TestTracingLayer { @@ -31,7 +71,7 @@ impl Layer for TestTracingLayer { fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { let level = *event.metadata().level(); if level <= self.panic_on_level { - panic!("logged on level {level}"); + panic!("Logged on level {level}."); } } } diff --git a/azalea-client/tests/change_dimension_to_nether_and_back.rs b/azalea-client/tests/change_dimension_to_nether_and_back.rs deleted file mode 100644 index 134c7cbf..00000000 --- a/azalea-client/tests/change_dimension_to_nether_and_back.rs +++ /dev/null @@ -1,154 +0,0 @@ -use azalea_client::{InConfigState, InGameState, test_utils::prelude::*}; -use azalea_core::position::ChunkPos; -use azalea_entity::LocalEntity; -use azalea_protocol::packets::{ - ConnectionProtocol, Packet, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, -}; -use azalea_registry::{DataRegistry, data::DimensionKind, identifier::Identifier}; -use azalea_world::InstanceName; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_change_dimension_to_nether_and_back() { - init_tracing(); - - generic_test_change_dimension_to_nether_and_back(true); - generic_test_change_dimension_to_nether_and_back(false); -} - -fn generic_test_change_dimension_to_nether_and_back(using_respawn: bool) { - let make_basic_login_or_respawn_packet = if using_respawn { - |dimension: DimensionKind, instance_name: Identifier| { - make_basic_respawn_packet(dimension, instance_name).into_variant() - } - } else { - |dimension: DimensionKind, instance_name: Identifier| { - make_basic_login_packet(dimension, instance_name).into_variant() - } - }; - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - assert!(simulation.has_component::()); - assert!(!simulation.has_component::()); - - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![ - ( - // this dimension should never be created. it just exists to make sure we're not - // hard-coding the dimension type id anywhere. - Identifier::new("azalea:fakedimension"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(16)), - ("min_y".into(), NbtTag::Int(0)), - ])), - ), - ( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - ), - ( - Identifier::new("minecraft:nether"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(256)), - ("min_y".into(), NbtTag::Int(0)), - ])), - ), - ] - .into_iter() - .collect(), - }); - simulation.tick(); - simulation.receive_packet(ClientboundFinishConfiguration); - simulation.tick(); - - assert!(!simulation.has_component::()); - assert!(simulation.has_component::()); - assert!(simulation.has_component::()); - - // - // OVERWORLD - // - - simulation.receive_packet(make_basic_login_packet( - DimensionKind::new_raw(1), // overworld - Identifier::new("azalea:a"), - )); - simulation.tick(); - - assert_eq!( - *simulation.component::(), - Identifier::new("azalea:a"), - "InstanceName should be azalea:a after setting dimension to that" - ); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - // make sure the chunk exists - simulation - .chunk(ChunkPos::new(0, 0)) - .expect("chunk should exist"); - - // - // NETHER - // - - simulation.receive_packet(make_basic_login_or_respawn_packet( - DimensionKind::new_raw(2), // nether - Identifier::new("azalea:b"), - )); - simulation.tick(); - - assert!( - simulation.chunk(ChunkPos::new(0, 0)).is_none(), - "chunk should not exist immediately after changing dimensions" - ); - assert_eq!( - *simulation.component::(), - Identifier::new("azalea:b"), - "InstanceName should be azalea:b after changing dimensions to that" - ); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), 256 / 16)); - simulation.tick(); - // make sure the chunk exists - simulation - .chunk(ChunkPos::new(0, 0)) - .expect("chunk should exist"); - simulation.receive_packet(make_basic_login_or_respawn_packet( - DimensionKind::new_raw(2), // nether - Identifier::new("minecraft:nether"), - )); - simulation.tick(); - - // - // BACK TO OVERWORLD - // - - simulation.receive_packet(make_basic_login_packet( - DimensionKind::new_raw(1), // overworld - Identifier::new("azalea:a"), - )); - simulation.tick(); - - assert_eq!( - *simulation.component::(), - Identifier::new("azalea:a"), - "InstanceName should be azalea:a after setting dimension back to that" - ); - assert!( - simulation.chunk(ChunkPos::new(0, 0)).is_none(), - "chunk should not exist immediately after switching back to overworld" - ); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - // make sure the chunk exists - simulation - .chunk(ChunkPos::new(0, 0)) - .expect("chunk should exist"); -} diff --git a/azalea-client/tests/client_disconnect.rs b/azalea-client/tests/client_disconnect.rs deleted file mode 100644 index fc17da0c..00000000 --- a/azalea-client/tests/client_disconnect.rs +++ /dev/null @@ -1,20 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_protocol::packets::ConnectionProtocol; -use azalea_world::InstanceName; - -#[test] -fn test_client_disconnect() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - - simulation.disconnect(); - simulation.tick(); - - // make sure we're disconnected - let is_connected = simulation.has_component::(); - assert!(!is_connected); - - // tick again to make sure nothing goes wrong - simulation.tick(); -} diff --git a/azalea-client/tests/close_open_container.rs b/azalea-client/tests/close_open_container.rs deleted file mode 100644 index 32a9874d..00000000 --- a/azalea-client/tests/close_open_container.rs +++ /dev/null @@ -1,67 +0,0 @@ -use azalea_chat::FormattedText; -use azalea_client::test_utils::prelude::*; -use azalea_core::position::ChunkPos; -use azalea_entity::inventory::Inventory; -use azalea_protocol::packets::{ - ConnectionProtocol, - game::{ClientboundContainerClose, ClientboundOpenScreen, ClientboundSetChunkCacheCenter}, -}; -use azalea_registry::builtin::MenuKind; - -#[test] -fn test_close_open_container() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - // receive a chunk so the player is "loaded" now - simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); - simulation.receive_packet(make_basic_empty_chunk( - ChunkPos::new(1, 23), - (384 + 64) / 16, - )); - simulation.tick(); - - // ensure no container is open - simulation.with_component(|inventory: &Inventory| { - assert!(inventory.container_menu.is_none()); - assert_eq!(inventory.id, 0); - }); - - // open a container - simulation.receive_packet(ClientboundOpenScreen { - container_id: 1, - menu_type: MenuKind::Generic9x3, - title: FormattedText::default(), - }); - simulation.tick(); - - simulation.with_component(|inventory: &Inventory| { - assert!(inventory.container_menu.is_some()); - assert_eq!(inventory.id, 1); - }); - - // close and open - simulation.receive_packet(ClientboundContainerClose { container_id: 1 }); - simulation.receive_packet(ClientboundOpenScreen { - container_id: 2, - menu_type: MenuKind::Generic9x3, - title: FormattedText::default(), - }); - simulation.tick(); - simulation.with_component(|inventory: &Inventory| { - // ensure that the new container was opened - assert!(inventory.container_menu.is_some()); - assert_eq!(inventory.id, 2); - }); - - // close with the wrong container id should still close - simulation.receive_packet(ClientboundContainerClose { container_id: 123 }); - simulation.tick(); - simulation.with_component(|inventory: &Inventory| { - assert!(inventory.container_menu.is_none()); - assert_eq!(inventory.id, 0); - }); -} diff --git a/azalea-client/tests/correct_movement.rs b/azalea-client/tests/correct_movement.rs deleted file mode 100644 index de494111..00000000 --- a/azalea-client/tests/correct_movement.rs +++ /dev/null @@ -1,79 +0,0 @@ -use azalea_client::{StartWalkEvent, WalkDirection, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos, Vec3}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ - ClientboundBlockUpdate, ClientboundPlayerPosition, ClientboundSetChunkCacheCenter, - ServerboundGamePacket, ServerboundMovePlayerPos, - }, - }, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_correct_movement() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // receive a chunk so the player is "loaded" now - simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); - simulation.receive_packet(make_basic_empty_chunk( - ChunkPos::new(1, 23), - (384 + 64) / 16, - )); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(31, 63, 370), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 1, - change: PositionMoveRotation { - pos: Vec3::new(31.5, 64., 370.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.tick(); - simulation.tick(); - - // walk for a tick - simulation.write_message(StartWalkEvent { - entity: simulation.entity, - direction: WalkDirection::Forward, - }); - sent_packets.clear(); - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!(p, ServerboundGamePacket::PlayerInput(_)) - }); - sent_packets.expect("MovePlayerPos { pos.z: 370.59800000336764, ... }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(ServerboundMovePlayerPos { - pos: Vec3 { - x: 31.5, - y: 64.0, - z: 370.59800000336764 - }, - flags: MoveFlags { - on_ground: true, - horizontal_collision: false - } - }) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/correct_sneak_movement.rs b/azalea-client/tests/correct_sneak_movement.rs deleted file mode 100644 index 1186ce8b..00000000 --- a/azalea-client/tests/correct_sneak_movement.rs +++ /dev/null @@ -1,111 +0,0 @@ -use azalea_client::{PhysicsState, StartWalkEvent, WalkDirection, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos, Vec3}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ - ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket, - ServerboundPlayerInput, - }, - }, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_correct_sneak_movement() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(0, 119, 0), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(0, 119, 1), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 1, - change: PositionMoveRotation { - pos: Vec3::new(0.5, 120., 0.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.tick(); - simulation.tick(); - simulation.tick(); - sent_packets.clear(); - - simulation.with_component_mut::(|p| p.trying_to_crouch = true); - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!( - p, - ServerboundGamePacket::PlayerInput(p) - if *p == ServerboundPlayerInput { shift: true, ..Default::default() } - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - simulation.write_message(StartWalkEvent { - entity: simulation.entity, - direction: WalkDirection::Forward, - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!( - p, - ServerboundGamePacket::PlayerInput(p) - if *p == ServerboundPlayerInput { forward: true, shift: true, ..Default::default() } - ) - }); - sent_packets.expect("MovePlayerPos { z: 0.5294000033944846 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 0.5294000033944846) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("MovePlayerPos { z: 0.5748524105068866 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 0.5748524105068866) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("MovePlayerPos: { z: 0.6290694310673044 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 0.6290694310673044) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/correct_sprint_sneak_movement.rs b/azalea-client/tests/correct_sprint_sneak_movement.rs deleted file mode 100644 index 8ab955b8..00000000 --- a/azalea-client/tests/correct_sprint_sneak_movement.rs +++ /dev/null @@ -1,129 +0,0 @@ -use azalea_client::{PhysicsState, SprintDirection, StartSprintEvent, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos, Vec3}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ - ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket, - ServerboundPlayerInput, - }, - }, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_correct_sprint_sneak_movement() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(0, 119, 0), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(0, 119, 1), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 1, - change: PositionMoveRotation { - pos: Vec3::new(0.5, 120., 0.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.tick(); - simulation.tick(); - simulation.tick(); - sent_packets.clear(); - - // start sprinting - simulation.write_message(StartSprintEvent { - entity: simulation.entity, - direction: SprintDirection::Forward, - }); - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!( - p, - ServerboundGamePacket::PlayerInput(p) - if *p == ServerboundPlayerInput { forward: true, sprint: true, ..Default::default() } - ) - }); - sent_packets.expect("PlayerCommand", |p| { - matches!(p, ServerboundGamePacket::PlayerCommand(_)) - }); - sent_packets.expect("MovePlayerPos { z: 0.6274000124096872 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 0.6274000124096872) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("MovePlayerPos { z: 0.8243604396746886 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 0.8243604396746886) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - simulation.with_component_mut::(|p| p.trying_to_crouch = true); - - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!( - p, - ServerboundGamePacket::PlayerInput(p) - if *p == ServerboundPlayerInput { forward: true, sprint: true, shift: true, ..Default::default() } - ) - }); - sent_packets.expect("MovePlayerPos { z: 1.0593008578621674 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 1.0593008578621674) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("MovePlayerPos { z: 1.2257983479146455 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 1.2257983479146455) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - sent_packets.expect("MovePlayerPos: { z: 1.3549259948648078 }", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.pos == Vec3::new(0.5, 120., 1.3549259948648078) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/despawn_entities_when_changing_dimension.rs b/azalea-client/tests/despawn_entities_when_changing_dimension.rs deleted file mode 100644 index 466da948..00000000 --- a/azalea-client/tests/despawn_entities_when_changing_dimension.rs +++ /dev/null @@ -1,81 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_core::position::ChunkPos; -use azalea_entity::metadata::Cow; -use azalea_protocol::packets::{ - ConnectionProtocol, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, -}; -use azalea_registry::{ - DataRegistry, builtin::EntityKind, data::DimensionKind, identifier::Identifier, -}; -use bevy_ecs::query::With; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_despawn_entities_when_changing_dimension() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![ - ( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - ), - ( - Identifier::new("minecraft:nether"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(256)), - ("min_y".into(), NbtTag::Int(0)), - ])), - ), - ] - .into_iter() - .collect(), - }); - simulation.tick(); - simulation.receive_packet(ClientboundFinishConfiguration); - simulation.tick(); - - // - // OVERWORLD - // - - simulation.receive_packet(make_basic_login_packet( - DimensionKind::new_raw(0), // overworld - Identifier::new("azalea:a"), - )); - simulation.tick(); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - // spawn a cow - simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); - simulation.tick(); - // make sure it's spawned - let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); - let cow_iter = cow_query.iter(simulation.app.world()); - assert_eq!(cow_iter.count(), 1, "cow should be spawned"); - - // - // NETHER - // - - simulation.receive_packet(make_basic_respawn_packet( - DimensionKind::new_raw(1), // nether - Identifier::new("azalea:b"), - )); - simulation.tick(); - - // cow should be completely deleted from the ecs - let cow_iter = cow_query.iter(simulation.app.world()); - assert_eq!( - cow_iter.count(), - 0, - "cow should be despawned after switching dimensions" - ); -} diff --git a/azalea-client/tests/enchantments.rs b/azalea-client/tests/enchantments.rs deleted file mode 100644 index 4486fba1..00000000 --- a/azalea-client/tests/enchantments.rs +++ /dev/null @@ -1,124 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_entity::Attributes; -use azalea_inventory::{ItemStack, components::Enchantments}; -use azalea_protocol::packets::{ - ConnectionProtocol, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::ClientboundContainerSetSlot, -}; -use azalea_registry::{Registry, builtin::ItemKind, data::Enchantment, identifier::Identifier}; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_enchantments() { - init_tracing(); - - let mut s = Simulation::new(ConnectionProtocol::Configuration); - s.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - )] - .into_iter() - .collect(), - }); - // actual registry data copied from vanilla - s.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:enchantment"), - entries: vec![( - Identifier::new("minecraft:efficiency"), - Some(NbtCompound::from([ - ( - "description", - [("translate", "enchantment.minecraft.efficiency".into())].into(), - ), - ("anvil_cost", 1.into()), - ( - "max_cost", - [("base", 51.into()), ("per_level_above_first", 10.into())].into(), - ), - ( - "min_cost", - [("base", 1.into()), ("per_level_above_first", 10.into())].into(), - ), - ( - "effects", - [( - "minecraft:attributes", - [ - ("operation", "add_value".into()), - ("attribute", "minecraft:mining_efficiency".into()), - ( - "amount", - [ - ("type", "minecraft:levels_squared".into()), - ("added", 1.0f32.into()), - ] - .into(), - ), - ("id", "minecraft:enchantment.efficiency".into()), - ] - .into(), - )] - .into(), - ), - ("max_level", 5.into()), - ("weight", 10.into()), - ("slots", ["mainhand"].into()), - ("supported_items", "#minecraft:enchantable/mining".into()), - ])), - )] - .into_iter() - .collect(), - }); - s.tick(); - s.receive_packet(ClientboundFinishConfiguration); - s.tick(); - s.receive_packet(default_login_packet()); - s.tick(); - - fn efficiency(simulation: &mut Simulation) -> f64 { - simulation.query_self::<&Attributes, _>(|c| c.mining_efficiency.calculate()) - } - - assert_eq!(efficiency(&mut s), 0.); - - s.receive_packet(ClientboundContainerSetSlot { - container_id: 0, - state_id: 1, - slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, - item_stack: ItemKind::DiamondPickaxe.into(), - }); - s.tick(); - - // still 0 efficiency - assert_eq!(efficiency(&mut s), 0.); - - s.receive_packet(ClientboundContainerSetSlot { - container_id: 0, - state_id: 2, - slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, - item_stack: ItemStack::from(ItemKind::DiamondPickaxe).with_component(Enchantments { - levels: [(Enchantment::from_u32(0).unwrap(), 1)].into(), - }), - }); - s.tick(); - - // level 1 gives us value 2 - assert_eq!(efficiency(&mut s), 2.); - - s.receive_packet(ClientboundContainerSetSlot { - container_id: 0, - state_id: 1, - slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, - item_stack: ItemKind::DiamondPickaxe.into(), - }); - s.tick(); - - // enchantment is cleared, so back to 0 - assert_eq!(efficiency(&mut s), 0.); -} diff --git a/azalea-client/tests/fast_login.rs b/azalea-client/tests/fast_login.rs deleted file mode 100644 index 5a653425..00000000 --- a/azalea-client/tests/fast_login.rs +++ /dev/null @@ -1,42 +0,0 @@ -use azalea_client::{InConfigState, test_utils::prelude::*}; -use azalea_entity::metadata::Health; -use azalea_protocol::packets::{ - ConnectionProtocol, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::ClientboundSetHealth, -}; -use azalea_registry::identifier::Identifier; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_fast_login() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - assert!(simulation.has_component::()); - - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - )] - .into_iter() - .collect(), - }); - - simulation.receive_packet(ClientboundFinishConfiguration); - // note that there's no simulation tick here - simulation.receive_packet(ClientboundSetHealth { - health: 15., - food: 20, - saturation: 20., - }); - simulation.tick(); - // we need a second tick to handle the state switch properly - simulation.tick(); - assert_eq!(*simulation.component::(), 15.); -} diff --git a/azalea-client/tests/login_to_dimension_with_same_name.rs b/azalea-client/tests/login_to_dimension_with_same_name.rs deleted file mode 100644 index 4adced62..00000000 --- a/azalea-client/tests/login_to_dimension_with_same_name.rs +++ /dev/null @@ -1,130 +0,0 @@ -use azalea_client::{ - InConfigState, InGameState, local_player::InstanceHolder, test_utils::prelude::*, -}; -use azalea_core::position::ChunkPos; -use azalea_entity::LocalEntity; -use azalea_protocol::packets::{ - ConnectionProtocol, Packet, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::ClientboundStartConfiguration, -}; -use azalea_registry::{DataRegistry, data::DimensionKind, identifier::Identifier}; -use azalea_world::InstanceName; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_login_to_dimension_with_same_name() { - init_tracing(); - - generic_test_login_to_dimension_with_same_name(true); - generic_test_login_to_dimension_with_same_name(false); -} - -fn generic_test_login_to_dimension_with_same_name(using_respawn: bool) { - let make_basic_login_or_respawn_packet = if using_respawn { - |dimension: DimensionKind, instance_name: Identifier| { - make_basic_respawn_packet(dimension, instance_name).into_variant() - } - } else { - |dimension: DimensionKind, instance_name: Identifier| { - make_basic_login_packet(dimension, instance_name).into_variant() - } - }; - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - assert!(simulation.has_component::()); - assert!(!simulation.has_component::()); - - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::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(); - - assert!(!simulation.has_component::()); - assert!(simulation.has_component::()); - assert!(simulation.has_component::()); - - // - // OVERWORLD 1 - // - - simulation.receive_packet(make_basic_login_packet( - DimensionKind::new_raw(0), // overworld - Identifier::new("azalea:overworld"), - )); - simulation.tick(); - - assert_eq!( - *simulation.component::(), - Identifier::new("azalea:overworld"), - "InstanceName should be azalea:overworld after setting dimension to that" - ); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - // make sure the chunk exists - simulation - .chunk(ChunkPos::new(0, 0)) - .expect("chunk should exist"); - - // - // OVERWORLD 2 - // - - simulation.receive_packet(ClientboundStartConfiguration); - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(256)), - ("min_y".into(), NbtTag::Int(0)), - ])), - )] - .into_iter() - .collect(), - }); - simulation.receive_packet(ClientboundFinishConfiguration); - simulation.receive_packet(make_basic_login_or_respawn_packet( - DimensionKind::new_raw(0), - Identifier::new("azalea:overworld"), - )); - simulation.tick(); - - assert!( - simulation.chunk(ChunkPos::new(0, 0)).is_none(), - "chunk should not exist immediately after changing dimensions" - ); - assert_eq!( - *simulation.component::(), - Identifier::new("azalea:overworld"), - "InstanceName should still be azalea:overworld after changing dimensions to that" - ); - assert_eq!( - simulation - .component::() - .instance - .read() - .chunks - .height, - 256 - ); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), 256 / 16)); - simulation.tick(); - // make sure the chunk exists - simulation - .chunk(ChunkPos::new(0, 0)) - .expect("chunk should exist"); -} diff --git a/azalea-client/tests/main.rs b/azalea-client/tests/main.rs new file mode 100644 index 00000000..4030c61d --- /dev/null +++ b/azalea-client/tests/main.rs @@ -0,0 +1 @@ +mod simulation; diff --git a/azalea-client/tests/mine_block_rollback.rs b/azalea-client/tests/mine_block_rollback.rs deleted file mode 100644 index 7d2ed1a5..00000000 --- a/azalea-client/tests/mine_block_rollback.rs +++ /dev/null @@ -1,44 +0,0 @@ -use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos}; -use azalea_protocol::packets::{ - ConnectionProtocol, - game::{ClientboundBlockChangedAck, ClientboundBlockUpdate}, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_mine_block_rollback() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.receive_packet(default_login_packet()); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - - let pos = BlockPos::new(1, 2, 3); - simulation.receive_packet(ClientboundBlockUpdate { - pos, - // tnt is used for this test because it's insta-mineable so we don't have to waste ticks - // waiting - block_state: BlockKind::Tnt.into(), - }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); - println!("set serverside tnt"); - - simulation.write_message(StartMiningBlockEvent { - entity: simulation.entity, - position: pos, - force: true, - }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); - println!("set clientside air"); - - // server didn't send the new block, so the change should be rolled back - simulation.receive_packet(ClientboundBlockChangedAck { seq: 1 }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); - println!("reset serverside tnt"); -} diff --git a/azalea-client/tests/mine_block_timing_hand.rs b/azalea-client/tests/mine_block_timing_hand.rs deleted file mode 100644 index d3dd9c30..00000000 --- a/azalea-client/tests/mine_block_timing_hand.rs +++ /dev/null @@ -1,155 +0,0 @@ -use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; -use azalea_core::{ - direction::Direction, - position::{BlockPos, ChunkPos, Vec3}, -}; -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::builtin::BlockKind; - -#[test] -fn test_mine_block_timing_hand() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - simulation.receive_packet(default_login_packet()); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - - let pos = BlockPos::new(0, 2, 0); - let pos2 = BlockPos::new(0, 1, 0); - simulation.receive_packet(ClientboundBlockUpdate { - pos, - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundBlockUpdate { - pos: pos2, - block_state: BlockKind::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(BlockKind::Stone.into()) - ); - println!("set serverside stone"); - simulation.with_component_mut::(|look| { - // look down - look.update_x_rot(90.); - }); - - simulation.tick(); - simulation.tick(); - simulation.tick(); - - simulation.write_message(StartMiningBlockEvent { - entity: simulation.entity, - position: pos, - force: false, - }); - sent_packets.clear(); - simulation.tick(); - sent_packets.expect("PlayerAction", |p| { - p == &ServerboundPlayerAction { - action: s_player_action::Action::StartDestroyBlock, - pos, - direction: Direction::Up, - seq: 1, - } - .into_variant() - }); - sent_packets.expect("Swing 1", |p| { - p == &ServerboundSwing { - hand: InteractionHand::MainHand, - } - .into_variant() - }); - sent_packets.expect("Swing 2", |p| { - p == &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 == &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 == &ServerboundSwing { - hand: InteractionHand::MainHand, - } - .into_variant() - }); - - for _ in 0..5 { - sent_packets.expect("MovePlayerPos", |p| { - matches!(p, ServerboundGamePacket::MovePlayerPos(_)) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - simulation.tick(); - } - - // mine the block again to make sure that it takes the same number of ticks - simulation.write_message(StartMiningBlockEvent { - entity: simulation.entity, - position: pos2, - force: false, - }); - for _ in 0..150 { - simulation.tick(); - } - sent_packets.clear(); - simulation.tick(); - - sent_packets.expect("PlayerAction { action: StopDestroyBlock }", |p| { - matches!( - p, - ServerboundGamePacket::PlayerAction(p) - if p.action == s_player_action::Action::StopDestroyBlock - ) - }); -} diff --git a/azalea-client/tests/mine_block_without_rollback.rs b/azalea-client/tests/mine_block_without_rollback.rs deleted file mode 100644 index b6020ea2..00000000 --- a/azalea-client/tests/mine_block_without_rollback.rs +++ /dev/null @@ -1,46 +0,0 @@ -use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos}; -use azalea_protocol::packets::{ - ConnectionProtocol, - game::{ClientboundBlockChangedAck, ClientboundBlockUpdate}, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_mine_block_without_rollback() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.receive_packet(default_login_packet()); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - - let pos = BlockPos::new(1, 2, 3); - simulation.receive_packet(ClientboundBlockUpdate { - pos, - // tnt is used for this test because it's insta-mineable so we don't have to waste ticks - // waiting - block_state: BlockKind::Tnt.into(), - }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); - - simulation.write_message(StartMiningBlockEvent { - entity: simulation.entity, - position: pos, - force: true, - }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); - - // server acknowledged our change by sending a BlockUpdate + BlockChangedAck, so - // no rollback - simulation.receive_packet(ClientboundBlockUpdate { - pos, - block_state: BlockKind::Air.into(), - }); - simulation.receive_packet(ClientboundBlockChangedAck { seq: 1 }); - simulation.tick(); - assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); -} diff --git a/azalea-client/tests/move_and_despawn_entity.rs b/azalea-client/tests/move_and_despawn_entity.rs deleted file mode 100644 index 8a143243..00000000 --- a/azalea-client/tests/move_and_despawn_entity.rs +++ /dev/null @@ -1,40 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_core::position::{ChunkPos, Vec3}; -use azalea_protocol::{ - common::movements::{PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ClientboundRemoveEntities, ClientboundTeleportEntity}, - }, -}; -use azalea_registry::builtin::EntityKind; -use azalea_world::MinecraftEntityId; - -#[test] -fn test_move_and_despawn_entity() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.receive_packet(default_login_packet()); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - - simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); - simulation.tick(); - - simulation.receive_packet(ClientboundTeleportEntity { - id: MinecraftEntityId(123), - change: PositionMoveRotation { - pos: Vec3::new(16., 0., 0.), - delta: Vec3::ZERO, - look_direction: Default::default(), - }, - relative: RelativeMovements::all_relative(), - on_ground: true, - }); - simulation.receive_packet(ClientboundRemoveEntities { - entity_ids: vec![MinecraftEntityId(123)], - }); - simulation.tick(); -} diff --git a/azalea-client/tests/move_despawned_entity.rs b/azalea-client/tests/move_despawned_entity.rs deleted file mode 100644 index dad2a710..00000000 --- a/azalea-client/tests/move_despawned_entity.rs +++ /dev/null @@ -1,45 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_core::position::ChunkPos; -use azalea_entity::metadata::Cow; -use azalea_protocol::packets::{ConnectionProtocol, game::ClientboundMoveEntityRot}; -use azalea_registry::builtin::EntityKind; -use azalea_world::MinecraftEntityId; -use bevy_ecs::query::With; -use tracing::Level; - -#[test] -fn test_move_despawned_entity() { - init_tracing_with_level(Level::ERROR); // a warning is expected here - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.receive_packet(default_login_packet()); - - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.tick(); - // spawn a cow - simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); - simulation.tick(); - - // make sure it's spawned - let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); - let cow_iter = cow_query.iter(simulation.app.world()); - assert_eq!(cow_iter.count(), 1, "cow should be spawned"); - - // despawn the cow by receiving a login packet - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - // make sure it's despawned - let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); - let cow_iter = cow_query.iter(simulation.app.world()); - assert_eq!(cow_iter.count(), 0, "cow should be despawned"); - - // send a move_entity_rot - simulation.receive_packet(ClientboundMoveEntityRot { - entity_id: MinecraftEntityId(123), - y_rot: 0, - x_rot: 0, - on_ground: false, - }); - simulation.tick(); -} diff --git a/azalea-client/tests/packet_order.rs b/azalea-client/tests/packet_order.rs deleted file mode 100644 index 619ed0b6..00000000 --- a/azalea-client/tests/packet_order.rs +++ /dev/null @@ -1,128 +0,0 @@ -use azalea_client::{SprintDirection, StartSprintEvent, test_utils::prelude::*}; -use azalea_core::position::{BlockPos, ChunkPos, Vec3}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ - ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundAcceptTeleportation, - ServerboundGamePacket, ServerboundMovePlayerPosRot, ServerboundMovePlayerStatusOnly, - }, - }, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_packet_order() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // receive a chunk so the player is "loaded" now - simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(1, 1, 3), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 1, - change: PositionMoveRotation { - pos: Vec3::new(1.5, 2., 3.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.tick(); - assert_eq!( - simulation.get_block_state(BlockPos::new(1, 1, 3)), - Some(BlockKind::Stone.into()) - ); - sent_packets.expect("AcceptTeleportation", |p| { - matches!( - p, - ServerboundGamePacket::AcceptTeleportation(ServerboundAcceptTeleportation { id: 1 }) - ) - }); - sent_packets.expect("MovePlayerPosRot", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPosRot(p) - if p == &ServerboundMovePlayerPosRot { - flags: MoveFlags { - on_ground: false, - horizontal_collision: false - }, - pos: Vec3::new(1.5, 2., 3.5), - look_direction: LookDirection::default(), - } - ) - }); - - // in vanilla these might be sent in a later tick (depending on how long it - // takes to render the chunks)... see the comment in player_loaded_packet. - // this might be worth changing later for better anticheat compat? - sent_packets.expect("PlayerLoaded", |p| { - matches!(p, ServerboundGamePacket::PlayerLoaded(_)) - }); - sent_packets.expect("MovePlayerPos", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p.flags == MoveFlags { - on_ground: false, - horizontal_collision: false - } - ) - }); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // it takes a tick for on_ground to be true - simulation.tick(); - sent_packets.expect("MovePlayerStatusOnly", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerStatusOnly(ServerboundMovePlayerStatusOnly { - flags: MoveFlags { - on_ground: true, - horizontal_collision: false - } - }) - ) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // make sure nothing happens now - simulation.tick(); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // now sprint for a tick - simulation.write_message(StartSprintEvent { - entity: simulation.entity, - direction: SprintDirection::Forward, - }); - simulation.tick(); - sent_packets.expect("PlayerInput", |p| { - matches!(p, ServerboundGamePacket::PlayerInput(_)) - }); - sent_packets.expect("PlayerCommand", |p| { - matches!(p, ServerboundGamePacket::PlayerCommand(_)) - }); - sent_packets.expect("MovePlayerPos", |p| { - matches!(p, ServerboundGamePacket::MovePlayerPos(_)) - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/packet_order_set_carried_item.rs b/azalea-client/tests/packet_order_set_carried_item.rs deleted file mode 100644 index e8ac386f..00000000 --- a/azalea-client/tests/packet_order_set_carried_item.rs +++ /dev/null @@ -1,110 +0,0 @@ -use azalea_client::{ - inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent, test_utils::prelude::*, -}; -use azalea_core::{ - direction::Direction, - position::{BlockPos, ChunkPos, Vec3}, -}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, Packet, - game::{ - ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundPlayerAction, - ServerboundSetCarriedItem, ServerboundSwing, s_interact::InteractionHand, - s_player_action, - }, - }, -}; -use azalea_registry::builtin::BlockKind; - -#[test] -fn test_packet_order_set_carried_item() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - simulation.receive_packet(default_login_packet()); - - 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: BlockKind::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(BlockKind::Stone.into()) - ); - simulation.with_component_mut::(|look| { - // look down - look.update_x_rot(90.); - }); - - simulation.tick(); - simulation.tick(); - simulation.tick(); - - simulation.trigger(SetSelectedHotbarSlotEvent { - entity: simulation.entity, - slot: 1, - }); - simulation.write_message(StartMiningBlockEvent { - entity: simulation.entity, - position: pos, - force: false, - }); - - sent_packets.clear(); - simulation.tick(); - sent_packets.expect("ServerboundPlayerAction", |p| { - p == &ServerboundPlayerAction { - action: s_player_action::Action::StartDestroyBlock, - pos, - direction: Direction::Up, - seq: 1, - } - .into_variant() - }); - sent_packets.expect("Swing 1", |p| { - p == &ServerboundSwing { - hand: InteractionHand::MainHand, - } - .into_variant() - }); - sent_packets.expect("SetCarriedItem", |p| { - p == &ServerboundSetCarriedItem { slot: 1 }.into_variant() - }); - sent_packets.expect("Swing 2", |p| { - p == &ServerboundSwing { - hand: InteractionHand::MainHand, - } - .into_variant() - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - simulation.tick(); - - sent_packets.expect("Swing", |p| { - p == &ServerboundSwing { - hand: InteractionHand::MainHand, - } - .into_variant() - }); - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/receive_spawn_entity_and_start_config_packet.rs b/azalea-client/tests/receive_spawn_entity_and_start_config_packet.rs deleted file mode 100644 index dfb1fef9..00000000 --- a/azalea-client/tests/receive_spawn_entity_and_start_config_packet.rs +++ /dev/null @@ -1,36 +0,0 @@ -use azalea_client::{InConfigState, test_utils::prelude::*}; -use azalea_core::position::Vec3; -use azalea_protocol::packets::{ - ConnectionProtocol, - game::{ClientboundAddEntity, ClientboundStartConfiguration}, -}; -use azalea_registry::builtin::EntityKind; -use azalea_world::InstanceName; -use uuid::Uuid; - -#[test] -fn test_receive_spawn_entity_and_start_config_packet() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.receive_packet(default_login_packet()); - simulation.tick(); - assert!(simulation.has_component::()); - simulation.tick(); - - simulation.receive_packet(ClientboundAddEntity { - id: 123.into(), - uuid: Uuid::new_v4(), - entity_type: EntityKind::ArmorStand, - position: Vec3::ZERO, - x_rot: 0, - y_rot: 0, - y_head_rot: 0, - data: 0, - movement: Default::default(), - }); - simulation.receive_packet(ClientboundStartConfiguration); - - simulation.tick(); - assert!(simulation.has_component::()); -} diff --git a/azalea-client/tests/receive_start_config_packet.rs b/azalea-client/tests/receive_start_config_packet.rs deleted file mode 100644 index bb948488..00000000 --- a/azalea-client/tests/receive_start_config_packet.rs +++ /dev/null @@ -1,20 +0,0 @@ -use azalea_client::{InConfigState, test_utils::prelude::*}; -use azalea_protocol::packets::{ConnectionProtocol, game::ClientboundStartConfiguration}; -use azalea_world::InstanceName; - -#[test] -fn test_receive_start_config_packet() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - assert!(simulation.has_component::()); - simulation.tick(); - - simulation.receive_packet(ClientboundStartConfiguration); - - simulation.tick(); - assert!(simulation.has_component::()); -} diff --git a/azalea-client/tests/reply_to_ping_with_pong.rs b/azalea-client/tests/reply_to_ping_with_pong.rs deleted file mode 100644 index 6b7cb4ca..00000000 --- a/azalea-client/tests/reply_to_ping_with_pong.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::sync::Arc; - -use azalea_client::{ - packet::{config::SendConfigPacketEvent, game::SendGamePacketEvent}, - test_utils::prelude::*, -}; -use azalea_protocol::packets::{ - ConnectionProtocol, - config::{ - self, ClientboundFinishConfiguration, ClientboundRegistryData, ServerboundConfigPacket, - }, - game::{self, ServerboundGamePacket}, -}; -use azalea_registry::identifier::Identifier; -use bevy_ecs::observer::On; -use parking_lot::Mutex; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn reply_to_ping_with_pong() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - - let reply_count = Arc::new(Mutex::new(0)); - let reply_count_clone = reply_count.clone(); - simulation - .app - .add_observer(move |send_config_packet: On| { - if send_config_packet.sent_by == simulation.entity - && let ServerboundConfigPacket::Pong(packet) = &send_config_packet.packet - { - assert_eq!(packet.id, 321); - *reply_count_clone.lock() += 1; - } - }); - - simulation.receive_packet(config::ClientboundPing { id: 321 }); - simulation.tick(); - assert_eq!(*reply_count.lock(), 1); - - // move into game state and test ClientboundPing there - - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - )] - .into_iter() - .collect(), - }); - - simulation.receive_packet(ClientboundFinishConfiguration); - simulation.tick(); - - let reply_count = Arc::new(Mutex::new(0)); - let reply_count_clone = reply_count.clone(); - simulation - .app - .add_observer(move |send_game_packet: On| { - if send_game_packet.sent_by == simulation.entity - && let ServerboundGamePacket::Pong(packet) = &send_game_packet.packet - { - assert_eq!(packet.id, 123); - *reply_count_clone.lock() += 1; - } - }); - - simulation.tick(); - simulation.receive_packet(game::ClientboundPing { id: 123 }); - simulation.tick(); - - assert_eq!(*reply_count.lock(), 1); -} diff --git a/azalea-client/tests/set_health_before_login.rs b/azalea-client/tests/set_health_before_login.rs deleted file mode 100644 index 4c1ec3f6..00000000 --- a/azalea-client/tests/set_health_before_login.rs +++ /dev/null @@ -1,50 +0,0 @@ -use azalea_client::{InConfigState, test_utils::prelude::*}; -use azalea_entity::{LocalEntity, metadata::Health}; -use azalea_protocol::packets::{ - ConnectionProtocol, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::ClientboundSetHealth, -}; -use azalea_registry::identifier::Identifier; -use simdnbt::owned::{NbtCompound, NbtTag}; - -#[test] -fn test_set_health_before_login() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - assert!(simulation.has_component::()); - - simulation.receive_packet(ClientboundRegistryData { - registry_id: Identifier::new("minecraft:dimension_type"), - entries: vec![( - Identifier::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(); - - assert!(!simulation.has_component::()); - assert!(simulation.has_component::()); - - simulation.receive_packet(ClientboundSetHealth { - health: 15., - food: 20, - saturation: 20., - }); - simulation.tick(); - assert_eq!(*simulation.component::(), 15.); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - // health should stay the same - assert_eq!(*simulation.component::(), 15.); -} diff --git a/azalea-client/tests/simulation/change_dimension_to_nether_and_back.rs b/azalea-client/tests/simulation/change_dimension_to_nether_and_back.rs new file mode 100644 index 00000000..2d4fb749 --- /dev/null +++ b/azalea-client/tests/simulation/change_dimension_to_nether_and_back.rs @@ -0,0 +1,154 @@ +use azalea_client::{InConfigState, InGameState, test_utils::prelude::*}; +use azalea_core::position::ChunkPos; +use azalea_entity::LocalEntity; +use azalea_protocol::packets::{ + ConnectionProtocol, Packet, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, +}; +use azalea_registry::{DataRegistry, data::DimensionKind, identifier::Identifier}; +use azalea_world::InstanceName; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_change_dimension_to_nether_and_back() { + let _lock = init(); + + generic_test_change_dimension_to_nether_and_back(true); + generic_test_change_dimension_to_nether_and_back(false); +} + +fn generic_test_change_dimension_to_nether_and_back(using_respawn: bool) { + let make_basic_login_or_respawn_packet = if using_respawn { + |dimension: DimensionKind, instance_name: Identifier| { + make_basic_respawn_packet(dimension, instance_name).into_variant() + } + } else { + |dimension: DimensionKind, instance_name: Identifier| { + make_basic_login_packet(dimension, instance_name).into_variant() + } + }; + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + assert!(!simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![ + ( + // this dimension should never be created. it just exists to make sure we're not + // hard-coding the dimension type id anywhere. + Identifier::new("azalea:fakedimension"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(16)), + ("min_y".into(), NbtTag::Int(0)), + ])), + ), + ( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + ), + ( + Identifier::new("minecraft:nether"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(256)), + ("min_y".into(), NbtTag::Int(0)), + ])), + ), + ] + .into_iter() + .collect(), + }); + simulation.tick(); + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.tick(); + + assert!(!simulation.has_component::()); + assert!(simulation.has_component::()); + assert!(simulation.has_component::()); + + // + // OVERWORLD + // + + simulation.receive_packet(make_basic_login_packet( + DimensionKind::new_raw(1), // overworld + Identifier::new("azalea:a"), + )); + simulation.tick(); + + assert_eq!( + *simulation.component::(), + Identifier::new("azalea:a"), + "InstanceName should be azalea:a after setting dimension to that" + ); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + // make sure the chunk exists + simulation + .chunk(ChunkPos::new(0, 0)) + .expect("chunk should exist"); + + // + // NETHER + // + + simulation.receive_packet(make_basic_login_or_respawn_packet( + DimensionKind::new_raw(2), // nether + Identifier::new("azalea:b"), + )); + simulation.tick(); + + assert!( + simulation.chunk(ChunkPos::new(0, 0)).is_none(), + "chunk should not exist immediately after changing dimensions" + ); + assert_eq!( + *simulation.component::(), + Identifier::new("azalea:b"), + "InstanceName should be azalea:b after changing dimensions to that" + ); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), 256 / 16)); + simulation.tick(); + // make sure the chunk exists + simulation + .chunk(ChunkPos::new(0, 0)) + .expect("chunk should exist"); + simulation.receive_packet(make_basic_login_or_respawn_packet( + DimensionKind::new_raw(2), // nether + Identifier::new("minecraft:nether"), + )); + simulation.tick(); + + // + // BACK TO OVERWORLD + // + + simulation.receive_packet(make_basic_login_packet( + DimensionKind::new_raw(1), // overworld + Identifier::new("azalea:a"), + )); + simulation.tick(); + + assert_eq!( + *simulation.component::(), + Identifier::new("azalea:a"), + "InstanceName should be azalea:a after setting dimension back to that" + ); + assert!( + simulation.chunk(ChunkPos::new(0, 0)).is_none(), + "chunk should not exist immediately after switching back to overworld" + ); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + // make sure the chunk exists + simulation + .chunk(ChunkPos::new(0, 0)) + .expect("chunk should exist"); +} diff --git a/azalea-client/tests/simulation/client_disconnect.rs b/azalea-client/tests/simulation/client_disconnect.rs new file mode 100644 index 00000000..0956fbfa --- /dev/null +++ b/azalea-client/tests/simulation/client_disconnect.rs @@ -0,0 +1,20 @@ +use azalea_client::test_utils::prelude::*; +use azalea_protocol::packets::ConnectionProtocol; +use azalea_world::InstanceName; + +#[test] +fn test_client_disconnect() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + + simulation.disconnect(); + simulation.tick(); + + // make sure we're disconnected + let is_connected = simulation.has_component::(); + assert!(!is_connected); + + // tick again to make sure nothing goes wrong + simulation.tick(); +} diff --git a/azalea-client/tests/simulation/close_open_container.rs b/azalea-client/tests/simulation/close_open_container.rs new file mode 100644 index 00000000..033451f1 --- /dev/null +++ b/azalea-client/tests/simulation/close_open_container.rs @@ -0,0 +1,67 @@ +use azalea_chat::FormattedText; +use azalea_client::test_utils::prelude::*; +use azalea_core::position::ChunkPos; +use azalea_entity::inventory::Inventory; +use azalea_protocol::packets::{ + ConnectionProtocol, + game::{ClientboundContainerClose, ClientboundOpenScreen, ClientboundSetChunkCacheCenter}, +}; +use azalea_registry::builtin::MenuKind; + +#[test] +fn test_close_open_container() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + // receive a chunk so the player is "loaded" now + simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); + simulation.receive_packet(make_basic_empty_chunk( + ChunkPos::new(1, 23), + (384 + 64) / 16, + )); + simulation.tick(); + + // ensure no container is open + simulation.with_component(|inventory: &Inventory| { + assert!(inventory.container_menu.is_none()); + assert_eq!(inventory.id, 0); + }); + + // open a container + simulation.receive_packet(ClientboundOpenScreen { + container_id: 1, + menu_type: MenuKind::Generic9x3, + title: FormattedText::default(), + }); + simulation.tick(); + + simulation.with_component(|inventory: &Inventory| { + assert!(inventory.container_menu.is_some()); + assert_eq!(inventory.id, 1); + }); + + // close and open + simulation.receive_packet(ClientboundContainerClose { container_id: 1 }); + simulation.receive_packet(ClientboundOpenScreen { + container_id: 2, + menu_type: MenuKind::Generic9x3, + title: FormattedText::default(), + }); + simulation.tick(); + simulation.with_component(|inventory: &Inventory| { + // ensure that the new container was opened + assert!(inventory.container_menu.is_some()); + assert_eq!(inventory.id, 2); + }); + + // close with the wrong container id should still close + simulation.receive_packet(ClientboundContainerClose { container_id: 123 }); + simulation.tick(); + simulation.with_component(|inventory: &Inventory| { + assert!(inventory.container_menu.is_none()); + assert_eq!(inventory.id, 0); + }); +} diff --git a/azalea-client/tests/simulation/correct_movement.rs b/azalea-client/tests/simulation/correct_movement.rs new file mode 100644 index 00000000..2a1a8f26 --- /dev/null +++ b/azalea-client/tests/simulation/correct_movement.rs @@ -0,0 +1,79 @@ +use azalea_client::{StartWalkEvent, WalkDirection, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos, Vec3}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ClientboundSetChunkCacheCenter, + ServerboundGamePacket, ServerboundMovePlayerPos, + }, + }, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_correct_movement() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // receive a chunk so the player is "loaded" now + simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); + simulation.receive_packet(make_basic_empty_chunk( + ChunkPos::new(1, 23), + (384 + 64) / 16, + )); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(31, 63, 370), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(31.5, 64., 370.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + simulation.tick(); + + // walk for a tick + simulation.write_message(StartWalkEvent { + entity: simulation.entity, + direction: WalkDirection::Forward, + }); + sent_packets.clear(); + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!(p, ServerboundGamePacket::PlayerInput(_)) + }); + sent_packets.expect("MovePlayerPos { pos.z: 370.59800000336764, ... }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(ServerboundMovePlayerPos { + pos: Vec3 { + x: 31.5, + y: 64.0, + z: 370.59800000336764 + }, + flags: MoveFlags { + on_ground: true, + horizontal_collision: false + } + }) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/correct_sneak_movement.rs b/azalea-client/tests/simulation/correct_sneak_movement.rs new file mode 100644 index 00000000..73abb26f --- /dev/null +++ b/azalea-client/tests/simulation/correct_sneak_movement.rs @@ -0,0 +1,111 @@ +use azalea_client::{PhysicsState, StartWalkEvent, WalkDirection, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos, Vec3}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket, + ServerboundPlayerInput, + }, + }, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_correct_sneak_movement() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(0, 119, 0), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(0, 119, 1), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(0.5, 120., 0.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + simulation.tick(); + simulation.tick(); + sent_packets.clear(); + + simulation.with_component_mut::(|p| p.trying_to_crouch = true); + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!( + p, + ServerboundGamePacket::PlayerInput(p) + if *p == ServerboundPlayerInput { shift: true, ..Default::default() } + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + simulation.write_message(StartWalkEvent { + entity: simulation.entity, + direction: WalkDirection::Forward, + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!( + p, + ServerboundGamePacket::PlayerInput(p) + if *p == ServerboundPlayerInput { forward: true, shift: true, ..Default::default() } + ) + }); + sent_packets.expect("MovePlayerPos { z: 0.5294000033944846 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 0.5294000033944846) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("MovePlayerPos { z: 0.5748524105068866 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 0.5748524105068866) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("MovePlayerPos: { z: 0.6290694310673044 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 0.6290694310673044) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/correct_sprint_sneak_movement.rs b/azalea-client/tests/simulation/correct_sprint_sneak_movement.rs new file mode 100644 index 00000000..a96c7024 --- /dev/null +++ b/azalea-client/tests/simulation/correct_sprint_sneak_movement.rs @@ -0,0 +1,129 @@ +use azalea_client::{PhysicsState, SprintDirection, StartSprintEvent, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos, Vec3}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundGamePacket, + ServerboundPlayerInput, + }, + }, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_correct_sprint_sneak_movement() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(0, 119, 0), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(0, 119, 1), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(0.5, 120., 0.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + simulation.tick(); + simulation.tick(); + sent_packets.clear(); + + // start sprinting + simulation.write_message(StartSprintEvent { + entity: simulation.entity, + direction: SprintDirection::Forward, + }); + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!( + p, + ServerboundGamePacket::PlayerInput(p) + if *p == ServerboundPlayerInput { forward: true, sprint: true, ..Default::default() } + ) + }); + sent_packets.expect("PlayerCommand", |p| { + matches!(p, ServerboundGamePacket::PlayerCommand(_)) + }); + sent_packets.expect("MovePlayerPos { z: 0.6274000124096872 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 0.6274000124096872) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("MovePlayerPos { z: 0.8243604396746886 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 0.8243604396746886) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + simulation.with_component_mut::(|p| p.trying_to_crouch = true); + + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!( + p, + ServerboundGamePacket::PlayerInput(p) + if *p == ServerboundPlayerInput { forward: true, sprint: true, shift: true, ..Default::default() } + ) + }); + sent_packets.expect("MovePlayerPos { z: 1.0593008578621674 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 1.0593008578621674) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("MovePlayerPos { z: 1.2257983479146455 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 1.2257983479146455) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + sent_packets.expect("MovePlayerPos: { z: 1.3549259948648078 }", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.pos == Vec3::new(0.5, 120., 1.3549259948648078) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/despawn_entities_when_changing_dimension.rs b/azalea-client/tests/simulation/despawn_entities_when_changing_dimension.rs new file mode 100644 index 00000000..8619bb2d --- /dev/null +++ b/azalea-client/tests/simulation/despawn_entities_when_changing_dimension.rs @@ -0,0 +1,81 @@ +use azalea_client::test_utils::prelude::*; +use azalea_core::position::ChunkPos; +use azalea_entity::metadata::Cow; +use azalea_protocol::packets::{ + ConnectionProtocol, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, +}; +use azalea_registry::{ + DataRegistry, builtin::EntityKind, data::DimensionKind, identifier::Identifier, +}; +use bevy_ecs::query::With; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_despawn_entities_when_changing_dimension() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![ + ( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + ), + ( + Identifier::new("minecraft:nether"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(256)), + ("min_y".into(), NbtTag::Int(0)), + ])), + ), + ] + .into_iter() + .collect(), + }); + simulation.tick(); + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.tick(); + + // + // OVERWORLD + // + + simulation.receive_packet(make_basic_login_packet( + DimensionKind::new_raw(0), // overworld + Identifier::new("azalea:a"), + )); + simulation.tick(); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + // spawn a cow + simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); + simulation.tick(); + // make sure it's spawned + let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); + let cow_iter = cow_query.iter(simulation.app.world()); + assert_eq!(cow_iter.count(), 1, "cow should be spawned"); + + // + // NETHER + // + + simulation.receive_packet(make_basic_respawn_packet( + DimensionKind::new_raw(1), // nether + Identifier::new("azalea:b"), + )); + simulation.tick(); + + // cow should be completely deleted from the ecs + let cow_iter = cow_query.iter(simulation.app.world()); + assert_eq!( + cow_iter.count(), + 0, + "cow should be despawned after switching dimensions" + ); +} diff --git a/azalea-client/tests/simulation/enchantments.rs b/azalea-client/tests/simulation/enchantments.rs new file mode 100644 index 00000000..f9834230 --- /dev/null +++ b/azalea-client/tests/simulation/enchantments.rs @@ -0,0 +1,124 @@ +use azalea_client::test_utils::prelude::*; +use azalea_entity::Attributes; +use azalea_inventory::{ItemStack, components::Enchantments}; +use azalea_protocol::packets::{ + ConnectionProtocol, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundContainerSetSlot, +}; +use azalea_registry::{Registry, builtin::ItemKind, data::Enchantment, identifier::Identifier}; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_enchantments() { + let _lock = init(); + + let mut s = Simulation::new(ConnectionProtocol::Configuration); + s.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + )] + .into_iter() + .collect(), + }); + // actual registry data copied from vanilla + s.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:enchantment"), + entries: vec![( + Identifier::new("minecraft:efficiency"), + Some(NbtCompound::from([ + ( + "description", + [("translate", "enchantment.minecraft.efficiency".into())].into(), + ), + ("anvil_cost", 1.into()), + ( + "max_cost", + [("base", 51.into()), ("per_level_above_first", 10.into())].into(), + ), + ( + "min_cost", + [("base", 1.into()), ("per_level_above_first", 10.into())].into(), + ), + ( + "effects", + [( + "minecraft:attributes", + [ + ("operation", "add_value".into()), + ("attribute", "minecraft:mining_efficiency".into()), + ( + "amount", + [ + ("type", "minecraft:levels_squared".into()), + ("added", 1.0f32.into()), + ] + .into(), + ), + ("id", "minecraft:enchantment.efficiency".into()), + ] + .into(), + )] + .into(), + ), + ("max_level", 5.into()), + ("weight", 10.into()), + ("slots", ["mainhand"].into()), + ("supported_items", "#minecraft:enchantable/mining".into()), + ])), + )] + .into_iter() + .collect(), + }); + s.tick(); + s.receive_packet(ClientboundFinishConfiguration); + s.tick(); + s.receive_packet(default_login_packet()); + s.tick(); + + fn efficiency(simulation: &mut Simulation) -> f64 { + simulation.query_self::<&Attributes, _>(|c| c.mining_efficiency.calculate()) + } + + assert_eq!(efficiency(&mut s), 0.); + + s.receive_packet(ClientboundContainerSetSlot { + container_id: 0, + state_id: 1, + slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, + item_stack: ItemKind::DiamondPickaxe.into(), + }); + s.tick(); + + // still 0 efficiency + assert_eq!(efficiency(&mut s), 0.); + + s.receive_packet(ClientboundContainerSetSlot { + container_id: 0, + state_id: 2, + slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, + item_stack: ItemStack::from(ItemKind::DiamondPickaxe).with_component(Enchantments { + levels: [(Enchantment::from_u32(0).unwrap(), 1)].into(), + }), + }); + s.tick(); + + // level 1 gives us value 2 + assert_eq!(efficiency(&mut s), 2.); + + s.receive_packet(ClientboundContainerSetSlot { + container_id: 0, + state_id: 1, + slot: *azalea_inventory::Player::HOTBAR_SLOTS.start() as u16, + item_stack: ItemKind::DiamondPickaxe.into(), + }); + s.tick(); + + // enchantment is cleared, so back to 0 + assert_eq!(efficiency(&mut s), 0.); +} diff --git a/azalea-client/tests/simulation/fast_login.rs b/azalea-client/tests/simulation/fast_login.rs new file mode 100644 index 00000000..270f4464 --- /dev/null +++ b/azalea-client/tests/simulation/fast_login.rs @@ -0,0 +1,42 @@ +use azalea_client::{InConfigState, test_utils::prelude::*}; +use azalea_entity::metadata::Health; +use azalea_protocol::packets::{ + ConnectionProtocol, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundSetHealth, +}; +use azalea_registry::identifier::Identifier; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_fast_login() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + )] + .into_iter() + .collect(), + }); + + simulation.receive_packet(ClientboundFinishConfiguration); + // note that there's no simulation tick here + simulation.receive_packet(ClientboundSetHealth { + health: 15., + food: 20, + saturation: 20., + }); + simulation.tick(); + // we need a second tick to handle the state switch properly + simulation.tick(); + assert_eq!(*simulation.component::(), 15.); +} diff --git a/azalea-client/tests/simulation/login_to_dimension_with_same_name.rs b/azalea-client/tests/simulation/login_to_dimension_with_same_name.rs new file mode 100644 index 00000000..917c50bb --- /dev/null +++ b/azalea-client/tests/simulation/login_to_dimension_with_same_name.rs @@ -0,0 +1,130 @@ +use azalea_client::{ + InConfigState, InGameState, local_player::InstanceHolder, test_utils::prelude::*, +}; +use azalea_core::position::ChunkPos; +use azalea_entity::LocalEntity; +use azalea_protocol::packets::{ + ConnectionProtocol, Packet, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundStartConfiguration, +}; +use azalea_registry::{DataRegistry, data::DimensionKind, identifier::Identifier}; +use azalea_world::InstanceName; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_login_to_dimension_with_same_name() { + let _lock = init(); + + generic_test_login_to_dimension_with_same_name(true); + generic_test_login_to_dimension_with_same_name(false); +} + +fn generic_test_login_to_dimension_with_same_name(using_respawn: bool) { + let make_basic_login_or_respawn_packet = if using_respawn { + |dimension: DimensionKind, instance_name: Identifier| { + make_basic_respawn_packet(dimension, instance_name).into_variant() + } + } else { + |dimension: DimensionKind, instance_name: Identifier| { + make_basic_login_packet(dimension, instance_name).into_variant() + } + }; + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + assert!(!simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::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(); + + assert!(!simulation.has_component::()); + assert!(simulation.has_component::()); + assert!(simulation.has_component::()); + + // + // OVERWORLD 1 + // + + simulation.receive_packet(make_basic_login_packet( + DimensionKind::new_raw(0), // overworld + Identifier::new("azalea:overworld"), + )); + simulation.tick(); + + assert_eq!( + *simulation.component::(), + Identifier::new("azalea:overworld"), + "InstanceName should be azalea:overworld after setting dimension to that" + ); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + // make sure the chunk exists + simulation + .chunk(ChunkPos::new(0, 0)) + .expect("chunk should exist"); + + // + // OVERWORLD 2 + // + + simulation.receive_packet(ClientboundStartConfiguration); + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(256)), + ("min_y".into(), NbtTag::Int(0)), + ])), + )] + .into_iter() + .collect(), + }); + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.receive_packet(make_basic_login_or_respawn_packet( + DimensionKind::new_raw(0), + Identifier::new("azalea:overworld"), + )); + simulation.tick(); + + assert!( + simulation.chunk(ChunkPos::new(0, 0)).is_none(), + "chunk should not exist immediately after changing dimensions" + ); + assert_eq!( + *simulation.component::(), + Identifier::new("azalea:overworld"), + "InstanceName should still be azalea:overworld after changing dimensions to that" + ); + assert_eq!( + simulation + .component::() + .instance + .read() + .chunks + .height, + 256 + ); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), 256 / 16)); + simulation.tick(); + // make sure the chunk exists + simulation + .chunk(ChunkPos::new(0, 0)) + .expect("chunk should exist"); +} diff --git a/azalea-client/tests/simulation/mine_block_rollback.rs b/azalea-client/tests/simulation/mine_block_rollback.rs new file mode 100644 index 00000000..98440f76 --- /dev/null +++ b/azalea-client/tests/simulation/mine_block_rollback.rs @@ -0,0 +1,44 @@ +use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos}; +use azalea_protocol::packets::{ + ConnectionProtocol, + game::{ClientboundBlockChangedAck, ClientboundBlockUpdate}, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_mine_block_rollback() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.receive_packet(default_login_packet()); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + + let pos = BlockPos::new(1, 2, 3); + simulation.receive_packet(ClientboundBlockUpdate { + pos, + // tnt is used for this test because it's insta-mineable so we don't have to waste ticks + // waiting + block_state: BlockKind::Tnt.into(), + }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); + println!("set serverside tnt"); + + simulation.write_message(StartMiningBlockEvent { + entity: simulation.entity, + position: pos, + force: true, + }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); + println!("set clientside air"); + + // server didn't send the new block, so the change should be rolled back + simulation.receive_packet(ClientboundBlockChangedAck { seq: 1 }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); + println!("reset serverside tnt"); +} diff --git a/azalea-client/tests/simulation/mine_block_timing_hand.rs b/azalea-client/tests/simulation/mine_block_timing_hand.rs new file mode 100644 index 00000000..53571089 --- /dev/null +++ b/azalea-client/tests/simulation/mine_block_timing_hand.rs @@ -0,0 +1,155 @@ +use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; +use azalea_core::{ + direction::Direction, + position::{BlockPos, ChunkPos, Vec3}, +}; +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::builtin::BlockKind; + +#[test] +fn test_mine_block_timing_hand() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + simulation.receive_packet(default_login_packet()); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + + let pos = BlockPos::new(0, 2, 0); + let pos2 = BlockPos::new(0, 1, 0); + simulation.receive_packet(ClientboundBlockUpdate { + pos, + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundBlockUpdate { + pos: pos2, + block_state: BlockKind::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(BlockKind::Stone.into()) + ); + println!("set serverside stone"); + simulation.with_component_mut::(|look| { + // look down + look.update_x_rot(90.); + }); + + simulation.tick(); + simulation.tick(); + simulation.tick(); + + simulation.write_message(StartMiningBlockEvent { + entity: simulation.entity, + position: pos, + force: false, + }); + sent_packets.clear(); + simulation.tick(); + sent_packets.expect("PlayerAction", |p| { + p == &ServerboundPlayerAction { + action: s_player_action::Action::StartDestroyBlock, + pos, + direction: Direction::Up, + seq: 1, + } + .into_variant() + }); + sent_packets.expect("Swing 1", |p| { + p == &ServerboundSwing { + hand: InteractionHand::MainHand, + } + .into_variant() + }); + sent_packets.expect("Swing 2", |p| { + p == &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 == &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 == &ServerboundSwing { + hand: InteractionHand::MainHand, + } + .into_variant() + }); + + for _ in 0..5 { + sent_packets.expect("MovePlayerPos", |p| { + matches!(p, ServerboundGamePacket::MovePlayerPos(_)) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + simulation.tick(); + } + + // mine the block again to make sure that it takes the same number of ticks + simulation.write_message(StartMiningBlockEvent { + entity: simulation.entity, + position: pos2, + force: false, + }); + for _ in 0..150 { + simulation.tick(); + } + sent_packets.clear(); + simulation.tick(); + + sent_packets.expect("PlayerAction { action: StopDestroyBlock }", |p| { + matches!( + p, + ServerboundGamePacket::PlayerAction(p) + if p.action == s_player_action::Action::StopDestroyBlock + ) + }); +} diff --git a/azalea-client/tests/simulation/mine_block_without_rollback.rs b/azalea-client/tests/simulation/mine_block_without_rollback.rs new file mode 100644 index 00000000..71f360c4 --- /dev/null +++ b/azalea-client/tests/simulation/mine_block_without_rollback.rs @@ -0,0 +1,46 @@ +use azalea_client::{mining::StartMiningBlockEvent, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos}; +use azalea_protocol::packets::{ + ConnectionProtocol, + game::{ClientboundBlockChangedAck, ClientboundBlockUpdate}, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_mine_block_without_rollback() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.receive_packet(default_login_packet()); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + + let pos = BlockPos::new(1, 2, 3); + simulation.receive_packet(ClientboundBlockUpdate { + pos, + // tnt is used for this test because it's insta-mineable so we don't have to waste ticks + // waiting + block_state: BlockKind::Tnt.into(), + }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Tnt.into())); + + simulation.write_message(StartMiningBlockEvent { + entity: simulation.entity, + position: pos, + force: true, + }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); + + // server acknowledged our change by sending a BlockUpdate + BlockChangedAck, so + // no rollback + simulation.receive_packet(ClientboundBlockUpdate { + pos, + block_state: BlockKind::Air.into(), + }); + simulation.receive_packet(ClientboundBlockChangedAck { seq: 1 }); + simulation.tick(); + assert_eq!(simulation.get_block_state(pos), Some(BlockKind::Air.into())); +} diff --git a/azalea-client/tests/simulation/mod.rs b/azalea-client/tests/simulation/mod.rs new file mode 100644 index 00000000..d090862b --- /dev/null +++ b/azalea-client/tests/simulation/mod.rs @@ -0,0 +1,25 @@ +// This file is @generated by `azalea-client/build.rs`. + +mod change_dimension_to_nether_and_back; +mod client_disconnect; +mod close_open_container; +mod correct_movement; +mod correct_sneak_movement; +mod correct_sprint_sneak_movement; +mod despawn_entities_when_changing_dimension; +mod enchantments; +mod fast_login; +mod login_to_dimension_with_same_name; +mod mine_block_rollback; +mod mine_block_timing_hand; +mod mine_block_without_rollback; +mod move_and_despawn_entity; +mod move_despawned_entity; +mod packet_order; +mod packet_order_set_carried_item; +mod receive_spawn_entity_and_start_config_packet; +mod receive_start_config_packet; +mod reply_to_ping_with_pong; +mod set_health_before_login; +mod teleport_movement; +mod ticks_alive; diff --git a/azalea-client/tests/simulation/move_and_despawn_entity.rs b/azalea-client/tests/simulation/move_and_despawn_entity.rs new file mode 100644 index 00000000..6c334a47 --- /dev/null +++ b/azalea-client/tests/simulation/move_and_despawn_entity.rs @@ -0,0 +1,40 @@ +use azalea_client::test_utils::prelude::*; +use azalea_core::position::{ChunkPos, Vec3}; +use azalea_protocol::{ + common::movements::{PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ClientboundRemoveEntities, ClientboundTeleportEntity}, + }, +}; +use azalea_registry::builtin::EntityKind; +use azalea_world::MinecraftEntityId; + +#[test] +fn test_move_and_despawn_entity() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.receive_packet(default_login_packet()); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + + simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); + simulation.tick(); + + simulation.receive_packet(ClientboundTeleportEntity { + id: MinecraftEntityId(123), + change: PositionMoveRotation { + pos: Vec3::new(16., 0., 0.), + delta: Vec3::ZERO, + look_direction: Default::default(), + }, + relative: RelativeMovements::all_relative(), + on_ground: true, + }); + simulation.receive_packet(ClientboundRemoveEntities { + entity_ids: vec![MinecraftEntityId(123)], + }); + simulation.tick(); +} diff --git a/azalea-client/tests/simulation/move_despawned_entity.rs b/azalea-client/tests/simulation/move_despawned_entity.rs new file mode 100644 index 00000000..7a171dae --- /dev/null +++ b/azalea-client/tests/simulation/move_despawned_entity.rs @@ -0,0 +1,45 @@ +use azalea_client::test_utils::prelude::*; +use azalea_core::position::ChunkPos; +use azalea_entity::metadata::Cow; +use azalea_protocol::packets::{ConnectionProtocol, game::ClientboundMoveEntityRot}; +use azalea_registry::builtin::EntityKind; +use azalea_world::MinecraftEntityId; +use bevy_ecs::query::With; +use tracing::Level; + +#[test] +fn test_move_despawned_entity() { + let _lock = init_with_level(Level::ERROR); // a warning is expected here + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.receive_packet(default_login_packet()); + + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.tick(); + // spawn a cow + simulation.receive_packet(make_basic_add_entity(EntityKind::Cow, 123, (0.5, 64., 0.5))); + simulation.tick(); + + // make sure it's spawned + let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); + let cow_iter = cow_query.iter(simulation.app.world()); + assert_eq!(cow_iter.count(), 1, "cow should be spawned"); + + // despawn the cow by receiving a login packet + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + // make sure it's despawned + let mut cow_query = simulation.app.world_mut().query_filtered::<(), With>(); + let cow_iter = cow_query.iter(simulation.app.world()); + assert_eq!(cow_iter.count(), 0, "cow should be despawned"); + + // send a move_entity_rot + simulation.receive_packet(ClientboundMoveEntityRot { + entity_id: MinecraftEntityId(123), + y_rot: 0, + x_rot: 0, + on_ground: false, + }); + simulation.tick(); +} diff --git a/azalea-client/tests/simulation/packet_order.rs b/azalea-client/tests/simulation/packet_order.rs new file mode 100644 index 00000000..ef99b938 --- /dev/null +++ b/azalea-client/tests/simulation/packet_order.rs @@ -0,0 +1,128 @@ +use azalea_client::{SprintDirection, StartSprintEvent, test_utils::prelude::*}; +use azalea_core::position::{BlockPos, ChunkPos, Vec3}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundAcceptTeleportation, + ServerboundGamePacket, ServerboundMovePlayerPosRot, ServerboundMovePlayerStatusOnly, + }, + }, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_packet_order() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // receive a chunk so the player is "loaded" now + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(1, 1, 3), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(1.5, 2., 3.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + assert_eq!( + simulation.get_block_state(BlockPos::new(1, 1, 3)), + Some(BlockKind::Stone.into()) + ); + sent_packets.expect("AcceptTeleportation", |p| { + matches!( + p, + ServerboundGamePacket::AcceptTeleportation(ServerboundAcceptTeleportation { id: 1 }) + ) + }); + sent_packets.expect("MovePlayerPosRot", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPosRot(p) + if p == &ServerboundMovePlayerPosRot { + flags: MoveFlags { + on_ground: false, + horizontal_collision: false + }, + pos: Vec3::new(1.5, 2., 3.5), + look_direction: LookDirection::default(), + } + ) + }); + + // in vanilla these might be sent in a later tick (depending on how long it + // takes to render the chunks)... see the comment in player_loaded_packet. + // this might be worth changing later for better anticheat compat? + sent_packets.expect("PlayerLoaded", |p| { + matches!(p, ServerboundGamePacket::PlayerLoaded(_)) + }); + sent_packets.expect("MovePlayerPos", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p.flags == MoveFlags { + on_ground: false, + horizontal_collision: false + } + ) + }); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // it takes a tick for on_ground to be true + simulation.tick(); + sent_packets.expect("MovePlayerStatusOnly", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerStatusOnly(ServerboundMovePlayerStatusOnly { + flags: MoveFlags { + on_ground: true, + horizontal_collision: false + } + }) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // make sure nothing happens now + simulation.tick(); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // now sprint for a tick + simulation.write_message(StartSprintEvent { + entity: simulation.entity, + direction: SprintDirection::Forward, + }); + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!(p, ServerboundGamePacket::PlayerInput(_)) + }); + sent_packets.expect("PlayerCommand", |p| { + matches!(p, ServerboundGamePacket::PlayerCommand(_)) + }); + sent_packets.expect("MovePlayerPos", |p| { + matches!(p, ServerboundGamePacket::MovePlayerPos(_)) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/packet_order_set_carried_item.rs b/azalea-client/tests/simulation/packet_order_set_carried_item.rs new file mode 100644 index 00000000..cae7c56a --- /dev/null +++ b/azalea-client/tests/simulation/packet_order_set_carried_item.rs @@ -0,0 +1,110 @@ +use azalea_client::{ + inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent, test_utils::prelude::*, +}; +use azalea_core::{ + direction::Direction, + position::{BlockPos, ChunkPos, Vec3}, +}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, Packet, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundPlayerAction, + ServerboundSetCarriedItem, ServerboundSwing, s_interact::InteractionHand, + s_player_action, + }, + }, +}; +use azalea_registry::builtin::BlockKind; + +#[test] +fn test_packet_order_set_carried_item() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + simulation.receive_packet(default_login_packet()); + + 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: BlockKind::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(BlockKind::Stone.into()) + ); + simulation.with_component_mut::(|look| { + // look down + look.update_x_rot(90.); + }); + + simulation.tick(); + simulation.tick(); + simulation.tick(); + + simulation.trigger(SetSelectedHotbarSlotEvent { + entity: simulation.entity, + slot: 1, + }); + simulation.write_message(StartMiningBlockEvent { + entity: simulation.entity, + position: pos, + force: false, + }); + + sent_packets.clear(); + simulation.tick(); + sent_packets.expect("ServerboundPlayerAction", |p| { + p == &ServerboundPlayerAction { + action: s_player_action::Action::StartDestroyBlock, + pos, + direction: Direction::Up, + seq: 1, + } + .into_variant() + }); + sent_packets.expect("Swing 1", |p| { + p == &ServerboundSwing { + hand: InteractionHand::MainHand, + } + .into_variant() + }); + sent_packets.expect("SetCarriedItem", |p| { + p == &ServerboundSetCarriedItem { slot: 1 }.into_variant() + }); + sent_packets.expect("Swing 2", |p| { + p == &ServerboundSwing { + hand: InteractionHand::MainHand, + } + .into_variant() + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + simulation.tick(); + + sent_packets.expect("Swing", |p| { + p == &ServerboundSwing { + hand: InteractionHand::MainHand, + } + .into_variant() + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/receive_spawn_entity_and_start_config_packet.rs b/azalea-client/tests/simulation/receive_spawn_entity_and_start_config_packet.rs new file mode 100644 index 00000000..13dd38fc --- /dev/null +++ b/azalea-client/tests/simulation/receive_spawn_entity_and_start_config_packet.rs @@ -0,0 +1,36 @@ +use azalea_client::{InConfigState, test_utils::prelude::*}; +use azalea_core::position::Vec3; +use azalea_protocol::packets::{ + ConnectionProtocol, + game::{ClientboundAddEntity, ClientboundStartConfiguration}, +}; +use azalea_registry::builtin::EntityKind; +use azalea_world::InstanceName; +use uuid::Uuid; + +#[test] +fn test_receive_spawn_entity_and_start_config_packet() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.receive_packet(default_login_packet()); + simulation.tick(); + assert!(simulation.has_component::()); + simulation.tick(); + + simulation.receive_packet(ClientboundAddEntity { + id: 123.into(), + uuid: Uuid::new_v4(), + entity_type: EntityKind::ArmorStand, + position: Vec3::ZERO, + x_rot: 0, + y_rot: 0, + y_head_rot: 0, + data: 0, + movement: Default::default(), + }); + simulation.receive_packet(ClientboundStartConfiguration); + + simulation.tick(); + assert!(simulation.has_component::()); +} diff --git a/azalea-client/tests/simulation/receive_start_config_packet.rs b/azalea-client/tests/simulation/receive_start_config_packet.rs new file mode 100644 index 00000000..f87d65da --- /dev/null +++ b/azalea-client/tests/simulation/receive_start_config_packet.rs @@ -0,0 +1,20 @@ +use azalea_client::{InConfigState, test_utils::prelude::*}; +use azalea_protocol::packets::{ConnectionProtocol, game::ClientboundStartConfiguration}; +use azalea_world::InstanceName; + +#[test] +fn test_receive_start_config_packet() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + assert!(simulation.has_component::()); + simulation.tick(); + + simulation.receive_packet(ClientboundStartConfiguration); + + simulation.tick(); + assert!(simulation.has_component::()); +} diff --git a/azalea-client/tests/simulation/reply_to_ping_with_pong.rs b/azalea-client/tests/simulation/reply_to_ping_with_pong.rs new file mode 100644 index 00000000..f77bf4bf --- /dev/null +++ b/azalea-client/tests/simulation/reply_to_ping_with_pong.rs @@ -0,0 +1,78 @@ +use std::sync::Arc; + +use azalea_client::{ + packet::{config::SendConfigPacketEvent, game::SendGamePacketEvent}, + test_utils::prelude::*, +}; +use azalea_protocol::packets::{ + ConnectionProtocol, + config::{ + self, ClientboundFinishConfiguration, ClientboundRegistryData, ServerboundConfigPacket, + }, + game::{self, ServerboundGamePacket}, +}; +use azalea_registry::identifier::Identifier; +use bevy_ecs::observer::On; +use parking_lot::Mutex; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn reply_to_ping_with_pong() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + + let reply_count = Arc::new(Mutex::new(0)); + let reply_count_clone = reply_count.clone(); + simulation + .app + .add_observer(move |send_config_packet: On| { + if send_config_packet.sent_by == simulation.entity + && let ServerboundConfigPacket::Pong(packet) = &send_config_packet.packet + { + assert_eq!(packet.id, 321); + *reply_count_clone.lock() += 1; + } + }); + + simulation.receive_packet(config::ClientboundPing { id: 321 }); + simulation.tick(); + assert_eq!(*reply_count.lock(), 1); + + // move into game state and test ClientboundPing there + + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + )] + .into_iter() + .collect(), + }); + + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.tick(); + + let reply_count = Arc::new(Mutex::new(0)); + let reply_count_clone = reply_count.clone(); + simulation + .app + .add_observer(move |send_game_packet: On| { + if send_game_packet.sent_by == simulation.entity + && let ServerboundGamePacket::Pong(packet) = &send_game_packet.packet + { + assert_eq!(packet.id, 123); + *reply_count_clone.lock() += 1; + } + }); + + simulation.tick(); + simulation.receive_packet(game::ClientboundPing { id: 123 }); + simulation.tick(); + + assert_eq!(*reply_count.lock(), 1); +} diff --git a/azalea-client/tests/simulation/set_health_before_login.rs b/azalea-client/tests/simulation/set_health_before_login.rs new file mode 100644 index 00000000..7d183b80 --- /dev/null +++ b/azalea-client/tests/simulation/set_health_before_login.rs @@ -0,0 +1,50 @@ +use azalea_client::{InConfigState, test_utils::prelude::*}; +use azalea_entity::{LocalEntity, metadata::Health}; +use azalea_protocol::packets::{ + ConnectionProtocol, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundSetHealth, +}; +use azalea_registry::identifier::Identifier; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_set_health_before_login() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: Identifier::new("minecraft:dimension_type"), + entries: vec![( + Identifier::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(); + + assert!(!simulation.has_component::()); + assert!(simulation.has_component::()); + + simulation.receive_packet(ClientboundSetHealth { + health: 15., + food: 20, + saturation: 20., + }); + simulation.tick(); + assert_eq!(*simulation.component::(), 15.); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + // health should stay the same + assert_eq!(*simulation.component::(), 15.); +} diff --git a/azalea-client/tests/simulation/teleport_movement.rs b/azalea-client/tests/simulation/teleport_movement.rs new file mode 100644 index 00000000..06a8f0b6 --- /dev/null +++ b/azalea-client/tests/simulation/teleport_movement.rs @@ -0,0 +1,158 @@ +use azalea_client::test_utils::prelude::*; +use azalea_core::{ + delta::{LpVec3, PositionDelta8}, + position::{BlockPos, ChunkPos, Vec3}, +}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + game::{ + ClientboundBlockUpdate, ClientboundForgetLevelChunk, ClientboundPing, + ClientboundPlayerPosition, ClientboundSetChunkCacheCenter, ClientboundSetEntityMotion, + ServerboundGamePacket, ServerboundMovePlayerPos, ServerboundMovePlayerPosRot, + }, + }, +}; +use azalea_registry::builtin::BlockKind; +use azalea_world::MinecraftEntityId; + +#[test] +fn test_teleport_movement() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // receive a chunk so the player is "loaded" now + simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); + simulation.receive_packet(make_basic_empty_chunk( + ChunkPos::new(1, 23), + (384 + 64) / 16, + )); + simulation.receive_packet(ClientboundBlockUpdate { + pos: BlockPos::new(31, 63, 370), + block_state: BlockKind::Stone.into(), + }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(31.5, 64., 370.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + simulation.tick(); + sent_packets.clear(); + + // now teleport to a far-away location + tracing::info!("meow!"); + simulation.receive_packet(ClientboundPing { id: 1 }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 2, + change: PositionMoveRotation { + pos: Vec3::new(10000.5, 70.0, 0.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.receive_packet(ClientboundPing { id: 2 }); + simulation.tick(); + sent_packets.expect_pong(1); + sent_packets.expect("AcceptTeleportation", |p| { + matches!(p, ServerboundGamePacket::AcceptTeleportation(_)) + }); + sent_packets.expect("MovePlayerPosRot", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPosRot(p) + if p == &ServerboundMovePlayerPosRot { + pos: Vec3::new(10000.5, 70.0, 0.5), + flags: MoveFlags::default(), + look_direction: LookDirection::default(), + } + ) + }); + sent_packets.expect_pong(2); + sent_packets.expect("MovePlayerPos", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p == &ServerboundMovePlayerPos { + pos: Vec3::new(10000.5, 70.0, 0.5), + flags: MoveFlags::default() + } + ) + }); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // + + simulation.receive_packet(ClientboundForgetLevelChunk { + pos: ChunkPos { x: 1, z: 23 }, + }); + simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 625, z: 0 }); + simulation.receive_packet(ClientboundPing { id: 3 }); + simulation.receive_packet(ClientboundPlayerPosition { + id: 3, + change: PositionMoveRotation { + pos: Vec3::new(10000.5, 70.0000001, 0.5), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.receive_packet(ClientboundPing { id: 4 }); + simulation.receive_packet(ClientboundSetEntityMotion { + id: MinecraftEntityId(0), + delta: LpVec3::from(Vec3::from(PositionDelta8 { + xa: 0, + ya: -627, + za: 0, + })), + }); + simulation.receive_packet(ClientboundPing { id: 5 }); + simulation.tick(); + + sent_packets.expect_pong(3); + sent_packets.expect("AcceptTeleportation", |p| { + matches!(p, ServerboundGamePacket::AcceptTeleportation(_)) + }); + sent_packets.expect("MovePlayerPosRot", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPosRot(p) + if p == &ServerboundMovePlayerPosRot { + pos: Vec3::new(10000.5, 70.0000001, 0.5), + flags: MoveFlags::default(), + look_direction: LookDirection::default(), + }) + }); + sent_packets.expect_pong(4); + sent_packets.expect_pong(5); + sent_packets.expect("MovePlayerPos", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerPos(p) + if p == &ServerboundMovePlayerPos { + pos: Vec3::new(10000.5, 69.84691458452664, 0.5), + flags: MoveFlags::default() + } + ) + }); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} diff --git a/azalea-client/tests/simulation/ticks_alive.rs b/azalea-client/tests/simulation/ticks_alive.rs new file mode 100644 index 00000000..655504db --- /dev/null +++ b/azalea-client/tests/simulation/ticks_alive.rs @@ -0,0 +1,32 @@ +use azalea_client::{test_utils::prelude::*, tick_counter::TicksConnected}; +use azalea_protocol::packets::ConnectionProtocol; + +#[test] +fn counter_increments_and_resets_on_disconnect() { + let _lock = init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Game); + simulation.tick(); + + assert!(!simulation.has_component::()); + simulation.receive_packet(default_login_packet()); + simulation.tick(); + + assert!(simulation.has_component::()); + assert_eq!(simulation.component::().0, 1); + + // Tick three times; counter should read 2, 3, 4. + for expected in 2..=4 { + simulation.tick(); + let counter = simulation.component::(); + assert_eq!( + counter.0, expected, + "after {expected} tick(s) counter should be {expected}" + ); + } + + simulation.disconnect(); + simulation.tick(); + + assert!(!simulation.has_component::()); +} diff --git a/azalea-client/tests/teleport_movement.rs b/azalea-client/tests/teleport_movement.rs deleted file mode 100644 index 03df5b92..00000000 --- a/azalea-client/tests/teleport_movement.rs +++ /dev/null @@ -1,158 +0,0 @@ -use azalea_client::test_utils::prelude::*; -use azalea_core::{ - delta::{LpVec3, PositionDelta8}, - position::{BlockPos, ChunkPos, Vec3}, -}; -use azalea_entity::LookDirection; -use azalea_protocol::{ - common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, - packets::{ - ConnectionProtocol, - game::{ - ClientboundBlockUpdate, ClientboundForgetLevelChunk, ClientboundPing, - ClientboundPlayerPosition, ClientboundSetChunkCacheCenter, ClientboundSetEntityMotion, - ServerboundGamePacket, ServerboundMovePlayerPos, ServerboundMovePlayerPosRot, - }, - }, -}; -use azalea_registry::builtin::BlockKind; -use azalea_world::MinecraftEntityId; - -#[test] -fn test_teleport_movement() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - let sent_packets = SentPackets::new(&mut simulation); - - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // receive a chunk so the player is "loaded" now - simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 1, z: 23 }); - simulation.receive_packet(make_basic_empty_chunk( - ChunkPos::new(1, 23), - (384 + 64) / 16, - )); - simulation.receive_packet(ClientboundBlockUpdate { - pos: BlockPos::new(31, 63, 370), - block_state: BlockKind::Stone.into(), - }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 1, - change: PositionMoveRotation { - pos: Vec3::new(31.5, 64., 370.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.tick(); - simulation.tick(); - sent_packets.clear(); - - // now teleport to a far-away location - tracing::info!("meow!"); - simulation.receive_packet(ClientboundPing { id: 1 }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 2, - change: PositionMoveRotation { - pos: Vec3::new(10000.5, 70.0, 0.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.receive_packet(ClientboundPing { id: 2 }); - simulation.tick(); - sent_packets.expect_pong(1); - sent_packets.expect("AcceptTeleportation", |p| { - matches!(p, ServerboundGamePacket::AcceptTeleportation(_)) - }); - sent_packets.expect("MovePlayerPosRot", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPosRot(p) - if p == &ServerboundMovePlayerPosRot { - pos: Vec3::new(10000.5, 70.0, 0.5), - flags: MoveFlags::default(), - look_direction: LookDirection::default(), - } - ) - }); - sent_packets.expect_pong(2); - sent_packets.expect("MovePlayerPos", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p == &ServerboundMovePlayerPos { - pos: Vec3::new(10000.5, 70.0, 0.5), - flags: MoveFlags::default() - } - ) - }); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); - - // - - simulation.receive_packet(ClientboundForgetLevelChunk { - pos: ChunkPos { x: 1, z: 23 }, - }); - simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 625, z: 0 }); - simulation.receive_packet(ClientboundPing { id: 3 }); - simulation.receive_packet(ClientboundPlayerPosition { - id: 3, - change: PositionMoveRotation { - pos: Vec3::new(10000.5, 70.0000001, 0.5), - delta: Vec3::ZERO, - look_direction: LookDirection::default(), - }, - relative: RelativeMovements::all_absolute(), - }); - simulation.receive_packet(ClientboundPing { id: 4 }); - simulation.receive_packet(ClientboundSetEntityMotion { - id: MinecraftEntityId(0), - delta: LpVec3::from(Vec3::from(PositionDelta8 { - xa: 0, - ya: -627, - za: 0, - })), - }); - simulation.receive_packet(ClientboundPing { id: 5 }); - simulation.tick(); - - sent_packets.expect_pong(3); - sent_packets.expect("AcceptTeleportation", |p| { - matches!(p, ServerboundGamePacket::AcceptTeleportation(_)) - }); - sent_packets.expect("MovePlayerPosRot", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPosRot(p) - if p == &ServerboundMovePlayerPosRot { - pos: Vec3::new(10000.5, 70.0000001, 0.5), - flags: MoveFlags::default(), - look_direction: LookDirection::default(), - }) - }); - sent_packets.expect_pong(4); - sent_packets.expect_pong(5); - sent_packets.expect("MovePlayerPos", |p| { - matches!( - p, - ServerboundGamePacket::MovePlayerPos(p) - if p == &ServerboundMovePlayerPos { - pos: Vec3::new(10000.5, 69.84691458452664, 0.5), - flags: MoveFlags::default() - } - ) - }); - - sent_packets.expect_tick_end(); - sent_packets.expect_empty(); -} diff --git a/azalea-client/tests/ticks_alive.rs b/azalea-client/tests/ticks_alive.rs deleted file mode 100644 index f2081002..00000000 --- a/azalea-client/tests/ticks_alive.rs +++ /dev/null @@ -1,32 +0,0 @@ -use azalea_client::{test_utils::prelude::*, tick_counter::TicksConnected}; -use azalea_protocol::packets::ConnectionProtocol; - -#[test] -fn counter_increments_and_resets_on_disconnect() { - init_tracing(); - - let mut simulation = Simulation::new(ConnectionProtocol::Game); - simulation.tick(); - - assert!(!simulation.has_component::()); - simulation.receive_packet(default_login_packet()); - simulation.tick(); - - assert!(simulation.has_component::()); - assert_eq!(simulation.component::().0, 1); - - // Tick three times; counter should read 2, 3, 4. - for expected in 2..=4 { - simulation.tick(); - let counter = simulation.component::(); - assert_eq!( - counter.0, expected, - "after {expected} tick(s) counter should be {expected}" - ); - } - - simulation.disconnect(); - simulation.tick(); - - assert!(!simulation.has_component::()); -} -- cgit v1.2.3