diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index a61c5d32..d07e323d 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -29,6 +29,9 @@ pub mod respawn; pub mod send_client_end; pub mod task_pool; +#[doc(hidden)] +pub mod test_simulation; + pub use account::{Account, AccountOpts}; pub use azalea_protocol::common::client_information::ClientInformation; pub use client::{ diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 207cdd91..73442804 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -246,17 +246,10 @@ pub fn process_packet_events(ecs: &mut World) { .insert(InstanceName(new_instance_name.clone())); } - let Some(dimension_type_element) = - instance_holder.instance.read().registries.dimension_type() + let Some((_dimension_type, dimension_data)) = p + .common + .dimension_type(&instance_holder.instance.read().registries) else { - error!("Server didn't send dimension type registry, can't log in"); - continue; - }; - - let dimension_name = ResourceLocation::new(&p.common.dimension.to_string()); - - let Some(dimension) = dimension_type_element.map.get(&dimension_name) else { - error!("No dimension_type with name {dimension_name}"); continue; }; @@ -264,8 +257,9 @@ pub fn process_packet_events(ecs: &mut World) { // there) let weak_instance = instance_container.insert( new_instance_name.clone(), - dimension.height, - dimension.min_y, + dimension_data.height, + dimension_data.min_y, + &instance_holder.instance.read().registries, ); instance_loaded_events.send(InstanceLoadedEvent { entity: player_entity, @@ -1387,17 +1381,10 @@ pub fn process_packet_events(ecs: &mut World) { { let new_instance_name = p.common.dimension.clone(); - let Some(dimension_type_element) = - instance_holder.instance.read().registries.dimension_type() + let Some((_dimension_type, dimension_data)) = p + .common + .dimension_type(&instance_holder.instance.read().registries) else { - error!("Server didn't send dimension type registry, can't log in."); - continue; - }; - - let dimension_name = ResourceLocation::new(&p.common.dimension.to_string()); - - let Some(dimension) = dimension_type_element.map.get(&dimension_name) else { - error!("No dimension_type with name {dimension_name}"); continue; }; @@ -1405,8 +1392,9 @@ pub fn process_packet_events(ecs: &mut World) { // there) let weak_instance = instance_container.insert( new_instance_name.clone(), - dimension.height, - dimension.min_y, + dimension_data.height, + dimension_data.min_y, + &instance_holder.instance.read().registries, ); instance_loaded_events.send(InstanceLoadedEvent { entity: player_entity, diff --git a/azalea-client/tests/simulation.rs b/azalea-client/src/test_simulation.rs similarity index 63% rename from azalea-client/tests/simulation.rs rename to azalea-client/src/test_simulation.rs index 593622aa..5afd8b00 100644 --- a/azalea-client/tests/simulation.rs +++ b/azalea-client/src/test_simulation.rs @@ -1,102 +1,106 @@ use std::{fmt::Debug, sync::Arc, time::Duration}; use azalea_auth::game_profile::GameProfile; -use azalea_client::{ +use azalea_buf::AzaleaWrite; +use azalea_core::game_type::{GameMode, OptionalGameType}; +use azalea_core::position::ChunkPos; +use azalea_core::resource_location::ResourceLocation; +use azalea_core::tick::GameTick; +use azalea_entity::metadata::PlayerMetadataBundle; +use azalea_protocol::packets::common::CommonPlayerSpawnInfo; +use azalea_protocol::packets::game::c_level_chunk_with_light::ClientboundLevelChunkPacketData; +use azalea_protocol::packets::game::c_light_update::ClientboundLightUpdatePacketData; +use azalea_protocol::packets::game::{ + ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn, +}; +use azalea_protocol::packets::{ConnectionProtocol, Packet, ProtocolPacket}; +use azalea_registry::DimensionType; +use azalea_world::palette::{PalettedContainer, PalettedContainerKind}; +use azalea_world::{Chunk, Instance, MinecraftEntityId, Section}; +use bevy_app::App; +use bevy_ecs::{prelude::*, schedule::ExecutorKind}; +use parking_lot::{Mutex, RwLock}; +use simdnbt::owned::Nbt; +use tokio::{sync::mpsc, time::sleep}; +use uuid::Uuid; + +use crate::{ events::LocalPlayerEvents, raw_connection::{RawConnection, RawConnectionReader, RawConnectionWriter}, ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle, }; -use azalea_core::{ - game_type::{GameMode, OptionalGameType}, - resource_location::ResourceLocation, - tick::GameTick, -}; -use azalea_entity::{ - metadata::{Health, PlayerMetadataBundle}, - LocalEntity, -}; -use azalea_protocol::packets::{ - common::CommonPlayerSpawnInfo, - config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::{ClientboundLogin, ClientboundSetHealth}, - ConnectionProtocol, Packet, ProtocolPacket, -}; -use azalea_registry::DimensionType; -use azalea_world::{Instance, MinecraftEntityId}; -use bevy_app::App; -use bevy_app::PluginGroup; -use bevy_ecs::{prelude::*, schedule::ExecutorKind}; -use bevy_log::{tracing_subscriber, LogPlugin}; -use parking_lot::{Mutex, RwLock}; -use simdnbt::owned::{NbtCompound, NbtTag}; -use tokio::{sync::mpsc, time::sleep}; -use uuid::Uuid; -#[test] -fn test_set_health_before_login() { - let _ = tracing_subscriber::fmt::try_init(); +/// A way to simulate a client in a server, used for some internal tests. +pub struct Simulation { + pub app: App, + pub entity: Entity, - let mut simulation = Simulation::new(ConnectionProtocol::Configuration); - assert!(simulation.has_component::()); + // the runtime needs to be kept around for the tasks to be considered alive + pub rt: tokio::runtime::Runtime, - simulation.receive_packet(ClientboundRegistryData { - registry_id: ResourceLocation::new("minecraft:dimension_type"), - entries: vec![( - ResourceLocation::new("minecraft:overworld"), - Some(NbtCompound::from_values(vec![ - ("height".into(), NbtTag::Int(384)), - ("min_y".into(), NbtTag::Int(-64)), - ])), - )] - .into_iter() - .collect(), - }); - simulation.tick(); - simulation.receive_packet(ClientboundFinishConfiguration); - simulation.tick(); - - 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(ClientboundLogin { - player_id: MinecraftEntityId(0), - hardcore: false, - levels: vec![], - max_players: 20, - chunk_radius: 8, - simulation_distance: 8, - reduced_debug_info: false, - show_death_screen: true, - do_limited_crafting: false, - common: CommonPlayerSpawnInfo { - dimension_type: DimensionType::Overworld, - dimension: ResourceLocation::new("minecraft:overworld"), - seed: 0, - game_type: GameMode::Survival, - previous_game_type: OptionalGameType(None), - is_debug: false, - is_flat: false, - last_death_location: None, - portal_cooldown: 0, - sea_level: 63, - }, - enforces_secure_chat: false, - }); - simulation.tick(); - - // health should stay the same - assert_eq!(*simulation.component::(), 15.); + pub incoming_packet_queue: Arc>>>, + pub outgoing_packets_receiver: mpsc::UnboundedReceiver>, } -pub fn create_local_player_bundle( +impl Simulation { + pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self { + let mut app = create_simulation_app(); + let mut entity = app.world_mut().spawn_empty(); + let (player, outgoing_packets_receiver, incoming_packet_queue, rt) = + create_local_player_bundle(entity.id(), initial_connection_protocol); + entity.insert(player); + + let entity = entity.id(); + + tick_app(&mut app); + + #[allow(clippy::single_match)] + match initial_connection_protocol { + ConnectionProtocol::Configuration => { + app.world_mut().entity_mut(entity).insert(InConfigState); + tick_app(&mut app); + } + _ => {} + } + + Self { + app, + entity, + rt, + incoming_packet_queue, + outgoing_packets_receiver, + } + } + + pub fn receive_packet(&mut self, packet: impl Packet

) { + let buf = azalea_protocol::write::serialize_packet(&packet.into_variant()).unwrap(); + self.incoming_packet_queue.lock().push(buf); + } + + pub fn tick(&mut self) { + tick_app(&mut self.app); + } + pub fn component(&self) -> T { + self.app.world().get::(self.entity).unwrap().clone() + } + pub fn get_component(&self) -> Option { + self.app.world().get::(self.entity).cloned() + } + pub fn has_component(&self) -> bool { + self.app.world().get::(self.entity).is_some() + } + + pub fn chunk(&self, chunk_pos: ChunkPos) -> Option>> { + self.component::() + .instance + .read() + .chunks + .get(&chunk_pos) + } +} + +#[allow(clippy::type_complexity)] +fn create_local_player_bundle( entity: Entity, connection_protocol: ConnectionProtocol, ) -> ( @@ -164,7 +168,12 @@ pub fn create_local_player_bundle( fn create_simulation_app() -> App { let mut app = App::new(); - app.add_plugins(azalea_client::DefaultPlugins.build().disable::()); + + #[cfg(feature = "log")] + app.add_plugins( + bevy_app::PluginGroup::build(crate::DefaultPlugins).disable::(), + ); + app.edit_schedule(bevy_app::Main, |schedule| { // makes test results more reproducible schedule.set_executor_kind(ExecutorKind::SingleThreaded); @@ -172,66 +181,85 @@ fn create_simulation_app() -> App { app } -pub struct Simulation { - pub app: App, - pub entity: Entity, - - // the runtime needs to be kept around for the tasks to be considered alive - pub rt: tokio::runtime::Runtime, - - pub incoming_packet_queue: Arc>>>, - pub outgoing_packets_receiver: mpsc::UnboundedReceiver>, -} - -impl Simulation { - pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self { - let mut app = create_simulation_app(); - let mut entity = app.world_mut().spawn_empty(); - let (player, outgoing_packets_receiver, incoming_packet_queue, rt) = - create_local_player_bundle(entity.id(), initial_connection_protocol); - entity.insert(player); - - let entity = entity.id(); - - tick_app(&mut app); - - match initial_connection_protocol { - ConnectionProtocol::Configuration => { - app.world_mut().entity_mut(entity).insert(InConfigState); - tick_app(&mut app); - } - _ => {} - } - - Self { - app, - entity, - rt, - incoming_packet_queue, - outgoing_packets_receiver, - } - } - - pub fn receive_packet(&mut self, packet: impl Packet

) { - let buf = azalea_protocol::write::serialize_packet(&packet.into_variant()).unwrap(); - self.incoming_packet_queue.lock().push(buf.into()); - } - - pub fn tick(&mut self) { - tick_app(&mut self.app); - } - pub fn component(&self) -> T { - self.app.world().get::(self.entity).unwrap().clone() - } - pub fn get_component(&self) -> Option { - self.app.world().get::(self.entity).cloned() - } - pub fn has_component(&self) -> bool { - self.app.world().get::(self.entity).is_some() - } -} - fn tick_app(app: &mut App) { app.update(); app.world_mut().run_schedule(GameTick); } + +pub fn make_basic_login_packet( + dimension_type: DimensionType, + dimension: ResourceLocation, +) -> ClientboundLogin { + ClientboundLogin { + player_id: MinecraftEntityId(0), + hardcore: false, + levels: vec![], + max_players: 20, + chunk_radius: 8, + simulation_distance: 8, + reduced_debug_info: false, + show_death_screen: true, + do_limited_crafting: false, + common: CommonPlayerSpawnInfo { + dimension_type, + dimension, + seed: 0, + game_type: GameMode::Survival, + previous_game_type: OptionalGameType(None), + is_debug: false, + is_flat: false, + last_death_location: None, + portal_cooldown: 0, + sea_level: 63, + }, + enforces_secure_chat: false, + } +} + +pub fn make_basic_respawn_packet( + dimension_type: DimensionType, + dimension: ResourceLocation, +) -> ClientboundRespawn { + ClientboundRespawn { + common: CommonPlayerSpawnInfo { + dimension_type, + dimension, + seed: 0, + game_type: GameMode::Survival, + previous_game_type: OptionalGameType(None), + is_debug: false, + is_flat: false, + last_death_location: None, + portal_cooldown: 0, + sea_level: 63, + }, + data_to_keep: 0, + } +} + +pub fn make_basic_empty_chunk( + pos: ChunkPos, + section_count: usize, +) -> ClientboundLevelChunkWithLight { + let mut chunk_bytes = Vec::new(); + let mut sections = Vec::new(); + for _ in 0..section_count { + sections.push(Section { + block_count: 0, + states: PalettedContainer::new(PalettedContainerKind::BlockStates), + biomes: PalettedContainer::new(PalettedContainerKind::Biomes), + }); + } + sections.azalea_write(&mut chunk_bytes).unwrap(); + + ClientboundLevelChunkWithLight { + x: pos.x, + z: pos.z, + chunk_data: ClientboundLevelChunkPacketData { + heightmaps: Nbt::None, + data: chunk_bytes, + block_entities: vec![], + }, + light_data: ClientboundLightUpdatePacketData::default(), + } +} diff --git a/azalea-client/tests/change_dimension_to_nether_and_back.rs b/azalea-client/tests/change_dimension_to_nether_and_back.rs new file mode 100644 index 00000000..3ed623ef --- /dev/null +++ b/azalea-client/tests/change_dimension_to_nether_and_back.rs @@ -0,0 +1,147 @@ +use azalea_client::{test_simulation::*, InConfigState}; +use azalea_core::{position::ChunkPos, resource_location::ResourceLocation}; +use azalea_entity::{metadata::Health, LocalEntity}; +use azalea_protocol::packets::{ + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundSetHealth, + ConnectionProtocol, +}; +use azalea_registry::DimensionType; +use azalea_world::InstanceName; +use bevy_log::tracing_subscriber; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_change_dimension_to_nether_and_back() { + let _ = tracing_subscriber::fmt::try_init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: ResourceLocation::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. + ResourceLocation::new("azalea:fakedimension"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(16)), + ("min_y".into(), NbtTag::Int(0)), + ])), + ), + ( + ResourceLocation::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + ), + ( + ResourceLocation::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::()); + + simulation.receive_packet(ClientboundSetHealth { + health: 15., + food: 20, + saturation: 20., + }); + simulation.tick(); + assert_eq!(*simulation.component::(), 15.); + + // + // OVERWORLD + // + + simulation.receive_packet(make_basic_login_packet( + DimensionType::new_raw(1), // overworld + ResourceLocation::new("azalea:a"), + )); + simulation.tick(); + + assert_eq!( + *simulation.component::(), + ResourceLocation::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_respawn_packet( + DimensionType::new_raw(2), // nether + ResourceLocation::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::(), + ResourceLocation::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_respawn_packet( + DimensionType::new_raw(2), // nether + ResourceLocation::new("minecraft:nether"), + )); + simulation.tick(); + + // + // BACK TO OVERWORLD + // + + simulation.receive_packet(make_basic_login_packet( + DimensionType::new_raw(1), // overworld + ResourceLocation::new("azalea:a"), + )); + simulation.tick(); + + assert_eq!( + *simulation.component::(), + ResourceLocation::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/set_health_before_login.rs b/azalea-client/tests/set_health_before_login.rs new file mode 100644 index 00000000..d6b6c426 --- /dev/null +++ b/azalea-client/tests/set_health_before_login.rs @@ -0,0 +1,55 @@ +use azalea_client::{test_simulation::*, InConfigState}; +use azalea_core::resource_location::ResourceLocation; +use azalea_entity::{metadata::Health, LocalEntity}; +use azalea_protocol::packets::{ + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::ClientboundSetHealth, + ConnectionProtocol, +}; +use azalea_registry::DimensionType; +use bevy_log::tracing_subscriber; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_set_health_before_login() { + let _ = tracing_subscriber::fmt::try_init(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + assert!(simulation.has_component::()); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: ResourceLocation::new("minecraft:dimension_type"), + entries: vec![( + ResourceLocation::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + )] + .into_iter() + .collect(), + }); + simulation.tick(); + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.tick(); + + 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(make_basic_login_packet( + DimensionType::new_raw(0), // overworld + ResourceLocation::new("minecraft:overworld"), + )); + simulation.tick(); + + // health should stay the same + assert_eq!(*simulation.component::(), 15.); +} diff --git a/azalea-core/src/data_registry.rs b/azalea-core/src/data_registry.rs index a72d9caf..6e2c29ff 100644 --- a/azalea-core/src/data_registry.rs +++ b/azalea-core/src/data_registry.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{io::Cursor, str::FromStr}; use azalea_registry::DataRegistry; use simdnbt::owned::NbtCompound; @@ -23,5 +23,25 @@ pub trait ResolvableDataRegistry: DataRegistry { let resolved = registry_values.get_index(self.protocol_id() as usize)?; Some(resolved) } + + fn resolve_and_deserialize( + &self, + registries: &RegistryHolder, + ) -> Option> { + let (name, value) = self.resolve(registries)?; + + let mut nbt_bytes = Vec::new(); + value.write(&mut nbt_bytes); + let nbt_borrow_compound = + simdnbt::borrow::read_compound(&mut Cursor::new(&nbt_bytes)).ok()?; + let value = match T::from_compound((&nbt_borrow_compound).into()) { + Ok(value) => value, + Err(err) => { + return Some(Err(err)); + } + }; + + Some(Ok((name.clone(), value))) + } } impl ResolvableDataRegistry for T {} diff --git a/azalea-core/src/registry_holder.rs b/azalea-core/src/registry_holder.rs index ec57eacc..b2f1f5b9 100644 --- a/azalea-core/src/registry_holder.rs +++ b/azalea-core/src/registry_holder.rs @@ -19,6 +19,11 @@ use crate::resource_location::ResourceLocation; /// The base of the registry. /// /// This is the registry that is sent to the client upon login. +/// +/// Note that `azalea-client` stores registries per-world instead of per-client +/// like you might expect. This is an optimization for swarms to reduce memory +/// usage, since registries are expected to be the same for every client in a +/// world. #[derive(Default, Debug, Clone)] pub struct RegistryHolder { pub map: HashMap>, diff --git a/azalea-physics/tests/physics.rs b/azalea-physics/tests/physics.rs index d2986ff0..c9fcdf97 100644 --- a/azalea-physics/tests/physics.rs +++ b/azalea-physics/tests/physics.rs @@ -1,12 +1,16 @@ +use std::sync::Arc; + use azalea_core::{ position::{BlockPos, ChunkPos, Vec3}, + registry_holder::RegistryHolder, resource_location::ResourceLocation, tick::GameTick, }; use azalea_entity::{EntityBundle, EntityPlugin, LocalEntity, Physics, Position}; use azalea_physics::PhysicsPlugin; -use azalea_world::{Chunk, InstanceContainer, MinecraftEntityId, PartialInstance}; +use azalea_world::{Chunk, Instance, InstanceContainer, MinecraftEntityId, PartialInstance}; use bevy_app::App; +use parking_lot::RwLock; use uuid::Uuid; /// You need an app to spawn entities in the world and do updates. @@ -17,14 +21,19 @@ fn make_test_app() -> App { app } -#[test] -fn test_gravity() { - let mut app = make_test_app(); - let world_lock = app.world_mut().resource_mut::().insert( +pub fn insert_overworld(app: &mut App) -> Arc> { + app.world_mut().resource_mut::().insert( ResourceLocation::new("minecraft:overworld"), 384, -64, - ); + &RegistryHolder::default(), + ) +} + +#[test] +fn test_gravity() { + let mut app = make_test_app(); + let world_lock = insert_overworld(&mut app); let mut partial_world = PartialInstance::default(); // the entity has to be in a loaded chunk for physics to work partial_world.chunks.set( @@ -80,11 +89,7 @@ fn test_gravity() { #[test] fn test_collision() { let mut app = make_test_app(); - let world_lock = app.world_mut().resource_mut::().insert( - ResourceLocation::new("minecraft:overworld"), - 384, - -64, - ); + let world_lock = insert_overworld(&mut app); let mut partial_world = PartialInstance::default(); partial_world.chunks.set( @@ -140,11 +145,7 @@ fn test_collision() { #[test] fn test_slab_collision() { let mut app = make_test_app(); - let world_lock = app.world_mut().resource_mut::().insert( - ResourceLocation::new("minecraft:overworld"), - 384, - -64, - ); + let world_lock = insert_overworld(&mut app); let mut partial_world = PartialInstance::default(); partial_world.chunks.set( @@ -194,11 +195,7 @@ fn test_slab_collision() { #[test] fn test_top_slab_collision() { let mut app = make_test_app(); - let world_lock = app.world_mut().resource_mut::().insert( - ResourceLocation::new("minecraft:overworld"), - 384, - -64, - ); + let world_lock = insert_overworld(&mut app); let mut partial_world = PartialInstance::default(); partial_world.chunks.set( @@ -251,6 +248,7 @@ fn test_weird_wall_collision() { ResourceLocation::new("minecraft:overworld"), 384, -64, + &RegistryHolder::default(), ); let mut partial_world = PartialInstance::default(); @@ -309,6 +307,7 @@ fn test_negative_coordinates_weird_wall_collision() { ResourceLocation::new("minecraft:overworld"), 384, -64, + &RegistryHolder::default(), ); let mut partial_world = PartialInstance::default(); @@ -371,6 +370,7 @@ fn spawn_and_unload_world() { ResourceLocation::new("minecraft:overworld"), 384, -64, + &RegistryHolder::default(), ); let mut partial_world = PartialInstance::default(); diff --git a/azalea-protocol/src/packets/common.rs b/azalea-protocol/src/packets/common.rs index fc78cd7a..ab9c46b4 100644 --- a/azalea-protocol/src/packets/common.rs +++ b/azalea-protocol/src/packets/common.rs @@ -1,9 +1,12 @@ use azalea_buf::AzBuf; use azalea_core::{ + data_registry::ResolvableDataRegistry, game_type::{GameMode, OptionalGameType}, position::GlobalPos, + registry_holder::{DimensionTypeElement, RegistryHolder}, resource_location::ResourceLocation, }; +use tracing::error; #[derive(Clone, Debug, AzBuf)] pub struct CommonPlayerSpawnInfo { @@ -20,3 +23,29 @@ pub struct CommonPlayerSpawnInfo { #[var] pub sea_level: i32, } +impl CommonPlayerSpawnInfo { + pub fn dimension_type( + &self, + registry_holder: &RegistryHolder, + ) -> Option<(ResourceLocation, DimensionTypeElement)> { + let dimension_res = self + .dimension_type + .resolve_and_deserialize::(registry_holder); + let Some(dimension_res) = dimension_res else { + error!("Couldn't resolve dimension_type {:?}", self.dimension_type); + return None; + }; + let (dimension_type, dimension_data) = match dimension_res { + Ok(d) => d, + Err(err) => { + error!( + "Couldn't deserialize dimension_type {:?}: {err:?}", + self.dimension_type + ); + return None; + } + }; + + Some((dimension_type, dimension_data)) + } +} diff --git a/azalea-protocol/src/packets/game/c_level_chunk_with_light.rs b/azalea-protocol/src/packets/game/c_level_chunk_with_light.rs index c9cdd508..9166e0eb 100755 --- a/azalea-protocol/src/packets/game/c_level_chunk_with_light.rs +++ b/azalea-protocol/src/packets/game/c_level_chunk_with_light.rs @@ -6,6 +6,7 @@ use super::c_light_update::ClientboundLightUpdatePacketData; #[derive(Clone, Debug, AzBuf, ClientboundGamePacket)] pub struct ClientboundLevelChunkWithLight { + // this can't be a ChunkPos since that reads z first and then x pub x: i32, pub z: i32, pub chunk_data: ClientboundLevelChunkPacketData, diff --git a/azalea-protocol/src/packets/game/c_light_update.rs b/azalea-protocol/src/packets/game/c_light_update.rs index 72523291..62b7a59d 100755 --- a/azalea-protocol/src/packets/game/c_light_update.rs +++ b/azalea-protocol/src/packets/game/c_light_update.rs @@ -11,7 +11,7 @@ pub struct ClientboundLightUpdate { pub light_data: ClientboundLightUpdatePacketData, } -#[derive(Clone, Debug, AzBuf)] +#[derive(Clone, Debug, AzBuf, Default)] pub struct ClientboundLightUpdatePacketData { pub sky_y_mask: BitSet, pub block_y_mask: BitSet, diff --git a/azalea-registry/src/data.rs b/azalea-registry/src/data.rs index c80889d6..17338c8f 100644 --- a/azalea-registry/src/data.rs +++ b/azalea-registry/src/data.rs @@ -22,3 +22,20 @@ impl DataRegistry for Enchantment { self.id } } + +#[derive(Debug, Clone, Copy, AzBuf, PartialEq, Eq, Hash)] +pub struct DimensionType { + #[var] + id: u32, +} +impl DimensionType { + pub fn new_raw(id: u32) -> Self { + Self { id } + } +} +impl DataRegistry for DimensionType { + const NAME: &'static str = "dimension_type"; + fn protocol_id(&self) -> u32 { + self.id + } +} diff --git a/azalea-registry/src/extra.rs b/azalea-registry/src/extra.rs index ba5d8559..1ba1f0a9 100644 --- a/azalea-registry/src/extra.rs +++ b/azalea-registry/src/extra.rs @@ -27,15 +27,6 @@ impl Default for WolfVariant { } } -registry! { -enum DimensionType { - Overworld => "minecraft:overworld", - Nether => "minecraft:the_nether", - End => "minecraft:the_end", - OverworldCaves => "minecraft:overworld_caves", -} -} - registry! { enum TrimMaterial { Quartz => "minecraft:quartz", diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 9fa5d7f9..f7f05a89 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -51,6 +51,7 @@ impl InstanceContainer { name: ResourceLocation, height: u32, min_y: i32, + default_registries: &RegistryHolder, ) -> Arc> { if let Some(existing_lock) = self.instances.get(&name).and_then(|world| world.upgrade()) { let existing = existing_lock.read(); @@ -72,7 +73,7 @@ impl InstanceContainer { chunks: ChunkStorage::new(height, min_y), entities_by_chunk: HashMap::new(), entity_by_id: IntMap::default(), - registries: RegistryHolder::default(), + registries: default_registries.clone(), })); debug!("Added new instance {name}"); self.instances.insert(name, Arc::downgrade(&world));