1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

Fix errors on switching dimensions (#204)

* Fix errors on switching dimensions

* fix other tests

* clippy

* fix log feature in test_simulation

* fix chunks oops
This commit is contained in:
mat 2025-02-21 13:43:56 -06:00 committed by GitHub
parent 63b1036a96
commit 833f306e8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 489 additions and 204 deletions

View file

@ -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::{

View file

@ -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,

View file

@ -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::<InConfigState>());
// 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::<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(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::<Health>(), 15.);
pub incoming_packet_queue: Arc<Mutex<Vec<Box<[u8]>>>>,
pub outgoing_packets_receiver: mpsc::UnboundedReceiver<Box<[u8]>>,
}
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<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)
}
}
#[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]>>,
}
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<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 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()
}
}
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(),
}
}

View file

@ -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");
}

View file

@ -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.);
}

View file

@ -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 {}

View file

@ -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>>,

View file

@ -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();

View file

@ -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))
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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
}
}

View file

@ -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",

View file

@ -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));