aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--azalea-client/src/lib.rs3
-rw-r--r--azalea-client/src/packet_handling/game.rs36
-rw-r--r--azalea-client/src/test_simulation.rs (renamed from azalea-client/tests/simulation.rs)302
-rw-r--r--azalea-client/tests/change_dimension_to_nether_and_back.rs147
-rw-r--r--azalea-client/tests/set_health_before_login.rs55
-rw-r--r--azalea-core/src/data_registry.rs22
-rw-r--r--azalea-core/src/registry_holder.rs5
-rw-r--r--azalea-physics/tests/physics.rs42
-rw-r--r--azalea-protocol/src/packets/common.rs29
-rwxr-xr-xazalea-protocol/src/packets/game/c_level_chunk_with_light.rs1
-rwxr-xr-xazalea-protocol/src/packets/game/c_light_update.rs2
-rw-r--r--azalea-registry/src/data.rs17
-rw-r--r--azalea-registry/src/extra.rs9
-rw-r--r--azalea-world/src/container.rs3
14 files changed, 479 insertions, 194 deletions
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
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::{
- 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_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::{Instance, MinecraftEntityId};
+use azalea_world::palette::{PalettedContainer, PalettedContainerKind};
+use azalea_world::{Chunk, Instance, MinecraftEntityId, Section};
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 simdnbt::owned::Nbt;
use tokio::{sync::mpsc, time::sleep};
use uuid::Uuid;
-#[test]
-fn test_set_health_before_login() {
- let _ = tracing_subscriber::fmt::try_init();
-
- let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
- assert!(simulation.has_component::<InConfigState>());
-
- 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();
+use crate::{
+ events::LocalPlayerEvents,
+ raw_connection::{RawConnection, RawConnectionReader, RawConnectionWriter},
+ ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle,
+};
- assert!(!simulation.has_component::<InConfigState>());
- assert!(simulation.has_component::<LocalEntity>());
+/// A way to simulate a client in a server, used for some internal tests.
+pub struct Simulation {
+ pub app: App,
+ pub entity: Entity,
- simulation.receive_packet(ClientboundSetHealth {
- health: 15.,
- food: 20,
- saturation: 20.,
- });
- simulation.tick();
- assert_eq!(*simulation.component::<Health>(), 15.);
+ // the runtime needs to be kept around for the tasks to be considered alive
+ pub rt: tokio::runtime::Runtime,
- 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();
+ pub incoming_packet_queue: Arc<Mutex<Vec<Box<[u8]>>>>,
+ pub outgoing_packets_receiver: mpsc::UnboundedReceiver<Box<[u8]>>,
+}
+
+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();
- // health should stay the same
- assert_eq!(*simulation.component::<Health>(), 15.);
+ 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<P: ProtocolPacket + Debug>(&mut self, packet: impl Packet<P>) {
+ 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<T: Component + Clone>(&self) -> T {
+ self.app.world().get::<T>(self.entity).unwrap().clone()
+ }
+ pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
+ self.app.world().get::<T>(self.entity).cloned()
+ }
+ pub fn has_component<T: Component>(&self) -> bool {
+ self.app.world().get::<T>(self.entity).is_some()
+ }
+
+ pub fn chunk(&self, chunk_pos: ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
+ self.component::<InstanceHolder>()
+ .instance
+ .read()
+ .chunks
+ .get(&chunk_pos)
+ }
}
-pub fn create_local_player_bundle(
+#[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::<LogPlugin>());
+
+ #[cfg(feature = "log")]
+ app.add_plugins(
+ bevy_app::PluginGroup::build(crate::DefaultPlugins).disable::<bevy_log::LogPlugin>(),
+ );
+
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<Mutex<Vec<Box<[u8]>>>>,
- pub outgoing_packets_receiver: mpsc::UnboundedReceiver<Box<[u8]>>,
+fn tick_app(app: &mut App) {
+ app.update();
+ app.world_mut().run_schedule(GameTick);
}
-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 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 receive_packet<P: ProtocolPacket + Debug>(&mut self, packet: impl Packet<P>) {
- let buf = azalea_protocol::write::serialize_packet(&packet.into_variant()).unwrap();
- self.incoming_packet_queue.lock().push(buf.into());
+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 tick(&mut self) {
- tick_app(&mut self.app);
- }
- pub fn component<T: Component + Clone>(&self) -> T {
- self.app.world().get::<T>(self.entity).unwrap().clone()
- }
- pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
- self.app.world().get::<T>(self.entity).cloned()
- }
- pub fn has_component<T: Component>(&self) -> bool {
- self.app.world().get::<T>(self.entity).is_some()
+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();
-fn tick_app(app: &mut App) {
- app.update();
- app.world_mut().run_schedule(GameTick);
+ 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::<InConfigState>());
+
+ 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::<InConfigState>());
+ assert!(simulation.has_component::<LocalEntity>());
+
+ simulation.receive_packet(ClientboundSetHealth {
+ health: 15.,
+ food: 20,
+ saturation: 20.,
+ });
+ simulation.tick();
+ assert_eq!(*simulation.component::<Health>(), 15.);
+
+ //
+ // OVERWORLD
+ //
+
+ simulation.receive_packet(make_basic_login_packet(
+ DimensionType::new_raw(1), // overworld
+ ResourceLocation::new("azalea:a"),
+ ));
+ simulation.tick();
+
+ assert_eq!(
+ *simulation.component::<InstanceName>(),
+ 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::<InstanceName>(),
+ 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::<InstanceName>(),
+ 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::<InConfigState>());
+
+ 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::<InConfigState>());
+ assert!(simulation.has_component::<LocalEntity>());
+
+ simulation.receive_packet(ClientboundSetHealth {
+ health: 15.,
+ food: 20,
+ saturation: 20.,
+ });
+ simulation.tick();
+ assert_eq!(*simulation.component::<Health>(), 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::<Health>(), 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<T: simdnbt::Deserialize>(
+ &self,
+ registries: &RegistryHolder,
+ ) -> Option<Result<(ResourceLocation, T), simdnbt::DeserializeError>> {
+ 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<T: DataRegistry> 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<ResourceLocation, IndexMap<ResourceLocation, NbtCompound>>,
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::<InstanceContainer>().insert(
+pub fn insert_overworld(app: &mut App) -> Arc<RwLock<Instance>> {
+ app.world_mut().resource_mut::<InstanceContainer>().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::<InstanceContainer>().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::<InstanceContainer>().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::<InstanceContainer>().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::<DimensionTypeElement>(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
@@ -28,15 +28,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",
Iron => "minecraft:iron",
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<RwLock<Instance>> {
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));