diff --git a/src/lobby.rs b/src/lobby.rs index 983fdc0..a19f192 100644 --- a/src/lobby.rs +++ b/src/lobby.rs @@ -30,7 +30,7 @@ use tokio::time; use crate::config::*; use crate::forge; -use crate::mc::{self, uuid}; +use crate::mc::{self, dimension, uuid}; use crate::net; use crate::proto; use crate::proto::client::{Client, ClientInfo, ClientState}; @@ -273,13 +273,14 @@ async fn send_lobby_join_game( writer: &mut WriteHalf<'_>, server: &Server, ) -> Result<(), ()> { - // Grab probed dimension codec + // Get dimension codec and build lobby dimension let dimension_codec: CompoundTag = if let Some(ref join_game) = server.probed_join_game.lock().await.as_ref() { join_game.dimension_codec.clone() } else { - snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")) + dimension::lobby_default_dimension_codec() }; + let dimension: CompoundTag = dimension::lobby_dimension(&dimension_codec); // Send Minecrafts default states, slightly customised for lobby world packet::write_packet( @@ -299,7 +300,7 @@ async fn send_lobby_join_game( "minecraft:the_end".into(), ], dimension_codec, - dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")), + dimension, world_name: "lazymc:lobby".into(), hashed_seed: 0, max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20), @@ -858,26 +859,3 @@ async fn drain_stream(reader: &mut ReadHalf<'_>) -> Result<(), ()> { } } } - -/// Read NBT CompoundTag from SNBT. -fn snbt_to_compound_tag(data: &str) -> CompoundTag { - use quartz_nbt::io::{write_nbt, Flavor}; - use quartz_nbt::snbt; - - // Parse SNBT data - let compound = snbt::parse(data).expect("failed to parse SNBT"); - - // Encode to binary - let mut binary = Vec::new(); - write_nbt(&mut binary, None, &compound, Flavor::Uncompressed) - .expect("failed to encode NBT CompoundTag as binary"); - - // Parse binary with usable NBT create - bin_to_compound_tag(&mut &*binary) -} - -/// Read NBT CompoundTag from SNBT. -fn bin_to_compound_tag(data: &[u8]) -> CompoundTag { - use nbt::decode::read_compound_tag; - read_compound_tag(&mut &*data).unwrap() -} diff --git a/src/mc/dimension.rs b/src/mc/dimension.rs new file mode 100644 index 0000000..f865a4f --- /dev/null +++ b/src/mc/dimension.rs @@ -0,0 +1,112 @@ +use nbt::CompoundTag; + +/// Create lobby dimension from the given codec. +/// +/// This creates a dimension suitable for the lobby that should be suitable for the current server +/// version. +pub fn lobby_dimension(codec: &CompoundTag) -> CompoundTag { + // Retrieve dimension types from codec + let dimension_types = match codec.get_compound_tag("minecraft:dimension_type") { + Ok(types) => types, + Err(_) => return lobby_default_dimension(), + }; + + // Get base dimension + let mut base = lobby_base_dimension(dimension_types); + + // Change known properties on base to get more desirable dimension + base.insert_i8("piglin_safe", 1); + base.insert_f32("ambient_light", 0.0); + // base.insert_str("infiniburn", "minecraft:infiniburn_end"); + base.insert_i8("respawn_anchor_works", 0); + base.insert_i8("has_skylight", 0); + base.insert_i8("bed_works", 0); + base.insert_str("effects", "minecraft:the_end"); + base.insert_i64("fixed_time", 0); + base.insert_i8("has_raids", 0); + base.insert_i32("min_y", 0); + base.insert_i32("height", 1); + base.insert_i32("logical_height", 1); + base.insert_f64("coordinate_scale", 1.0); + base.insert_i8("ultrawarm", 0); + base.insert_i8("has_ceiling", 0); + + base +} + +/// Get lobby base dimension. +/// +/// This retrieves the most desirable dimension to use as base for the lobby from the given list of +/// `dimension_types`. +/// +/// If no dimension is found in the given tag, a default one will be returned. +fn lobby_base_dimension(dimension_types: &CompoundTag) -> CompoundTag { + // The dimension types we prefer the most, in order + let preferred = vec![ + "minecraft:the_end", + "minecraft:the_nether", + "minecraft:the_overworld", + ]; + + let dimensions = dimension_types.get_compound_tag_vec("value").unwrap(); + + for name in preferred { + if let Some(dimension) = dimensions + .iter() + .find(|d| d.get_str("name").map(|n| n == name).unwrap_or(false)) + { + if let Ok(dimension) = dimension.get_compound_tag("element") { + return dimension.clone(); + } + } + } + + // Return first dimension + if let Some(dimension) = dimensions.first() { + if let Ok(dimension) = dimension.get_compound_tag("element") { + return dimension.clone(); + } + } + + // Fall back to default dimension + lobby_default_dimension() +} + +/// Default lobby dimension codec from resource file. +/// +/// This likely breaks if the Minecraft version doesn't match exactly. +/// Please use an up-to-date coded from the server instead. +pub fn lobby_default_dimension_codec() -> CompoundTag { + snbt_to_compound_tag(include_str!("../../res/dimension_codec.snbt")) +} + +/// Default lobby dimension from resource file. +/// +/// This likely breaks if the Minecraft version doesn't match exactly. +/// Please use `lobby_dimension` with an up-to-date coded from the server instead. +fn lobby_default_dimension() -> CompoundTag { + snbt_to_compound_tag(include_str!("../../res/dimension.snbt")) +} + +/// Read NBT CompoundTag from SNBT. +fn snbt_to_compound_tag(data: &str) -> CompoundTag { + use quartz_nbt::io::{write_nbt, Flavor}; + use quartz_nbt::snbt; + + // Parse SNBT data + let compound = snbt::parse(data).expect("failed to parse SNBT"); + + // Encode to binary + let mut binary = Vec::new(); + write_nbt(&mut binary, None, &compound, Flavor::Uncompressed) + .expect("failed to encode NBT CompoundTag as binary"); + + // Parse binary with usable NBT create + bin_to_compound_tag(&mut &*binary) +} + +/// Read NBT CompoundTag from SNBT. +fn bin_to_compound_tag(data: &[u8]) -> CompoundTag { + use nbt::decode::read_compound_tag; + read_compound_tag(&mut &*data).unwrap() +} diff --git a/src/mc/mod.rs b/src/mc/mod.rs index 617e738..a6111e6 100644 --- a/src/mc/mod.rs +++ b/src/mc/mod.rs @@ -1,4 +1,6 @@ pub mod ban; +#[cfg(feature = "lobby")] +pub mod dimension; pub mod favicon; #[cfg(feature = "rcon")] pub mod rcon;