diff --git a/examples/biomes.rs b/examples/biomes.rs index c5c8f70..38c5ce1 100644 --- a/examples/biomes.rs +++ b/examples/biomes.rs @@ -1,3 +1,4 @@ +use std::iter; use std::net::SocketAddr; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -43,7 +44,9 @@ struct ClientState { const MAX_PLAYERS: usize = 10; -const BIOME_COUNT: usize = 4 * 4; +const BIOME_COUNT: usize = 10; + +const MIN_Y: i32 = -64; #[async_trait] impl Config for Game { @@ -67,7 +70,7 @@ impl Config for Game { } fn biomes(&self) -> Vec { - (0..BIOME_COUNT) + (1..BIOME_COUNT) .map(|i| { let color = (0xffffff / BIOME_COUNT * i) as u32; Biome { @@ -81,6 +84,10 @@ impl Config for Game { ..Default::default() } }) + .chain(iter::once(Biome { + name: ident!("plains"), + ..Default::default() + })) .collect() } @@ -102,7 +109,9 @@ impl Config for Game { fn init(&self, server: &mut Server) { let world = server.worlds.insert(DimensionId::default(), ()).1; server.state.player_list = Some(server.player_lists.insert(()).0); - let height = server.shared.dimensions().next().unwrap().1.height as usize; + + let height = world.chunks.height(); + assert_eq!(world.chunks.min_y(), MIN_Y); for chunk_z in 0..3 { for chunk_x in 0..3 { @@ -112,16 +121,21 @@ impl Config for Game { // Set chunk blocks for z in 0..16 { for x in 0..16 { - chunk.set_block_state(x, 50, z, BlockState::GRASS_BLOCK); + chunk.set_block_state(x, 1, z, BlockState::GRASS_BLOCK); } } // Set chunk biomes for z in 0..4 { for x in 0..4 { - let biome_id = server.shared.biomes().nth(x + z * 4).unwrap().0; - for y in 0..height / 4 { + let biome_id = server + .shared + .biomes() + .nth((x + z * 4 + y * 4 * 4) % BIOME_COUNT) + .unwrap() + .0; + chunk.set_biome(x, y, z, biome_id); } } @@ -194,7 +208,7 @@ impl Config for Game { return false; } - if client.position().y <= -20.0 { + if client.position().y < MIN_Y as _ { client.teleport(spawn_pos, client.yaw(), client.pitch()); } diff --git a/examples/building.rs b/examples/building.rs index ce792e9..7242cc3 100644 --- a/examples/building.rs +++ b/examples/building.rs @@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use log::LevelFilter; use num::Integer; -use valence::biome::Biome; +use valence::async_trait; use valence::block::BlockState; use valence::chunk::{Chunk, UnloadedChunk}; use valence::client::{handle_event_default, ClientEvent, DiggingStatus, GameMode, Hand}; @@ -13,7 +13,6 @@ use valence::entity::{EntityId, EntityKind}; use valence::player_list::PlayerListId; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; -use valence::{async_trait, ident}; pub fn main() -> ShutdownResult { env_logger::Builder::new() @@ -68,14 +67,6 @@ impl Config for Game { }] } - fn biomes(&self) -> Vec { - vec![Biome { - name: ident!("valence:default_biome"), - grass_color: Some(0x00ff00), - ..Biome::default() - }] - } - async fn server_list_ping( &self, _server: &SharedServer, diff --git a/examples/conway.rs b/examples/conway.rs index 3673978..49f70eb 100644 --- a/examples/conway.rs +++ b/examples/conway.rs @@ -84,7 +84,7 @@ impl Config for Game { fn biomes(&self) -> Vec { vec![Biome { - name: ident!("valence:default_biome"), + name: ident!("plains"), grass_color: Some(0x00ff00), ..Biome::default() }] diff --git a/src/chunk.rs b/src/chunk.rs index 8f722b4..e92530a 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -12,7 +12,7 @@ use std::io::Write; use std::iter::FusedIterator; use bitvec::vec::BitVec; -use paletted_container::{PalettedContainer, PalettedContainerElement}; +use paletted_container::PalettedContainer; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; use valence_nbt::compound; @@ -505,26 +505,6 @@ impl Default for ChunkSection { const SECTION_BLOCK_COUNT: usize = 4096; const USIZE_BITS: usize = usize::BITS as _; -impl PalettedContainerElement for BlockState { - const DIRECT_BITS: usize = log2_ceil(BlockState::max_raw() as _); - const MAX_INDIRECT_BITS: usize = 8; - const MIN_INDIRECT_BITS: usize = 4; - - fn to_bits(self) -> u64 { - self.to_raw() as _ - } -} - -impl PalettedContainerElement for BiomeId { - const DIRECT_BITS: usize = 6; - const MAX_INDIRECT_BITS: usize = 4; - const MIN_INDIRECT_BITS: usize = 0; - - fn to_bits(self) -> u64 { - self.0 as _ - } -} - impl ChunkSection { fn mark_block_as_modified(&mut self, idx: usize) { if !self.is_block_modified(idx) { @@ -560,13 +540,35 @@ impl LoadedChunk { } /// Gets the chunk data packet for this chunk with the given position. - pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkDataAndUpdateLight { + pub(crate) fn chunk_data_packet( + &self, + pos: ChunkPos, + biome_registry_len: usize, + ) -> ChunkDataAndUpdateLight { let mut blocks_and_biomes = Vec::new(); for sect in self.sections.iter() { sect.non_air_count.encode(&mut blocks_and_biomes).unwrap(); - sect.block_states.encode(&mut blocks_and_biomes).unwrap(); - sect.biomes.encode(&mut blocks_and_biomes).unwrap(); + + sect.block_states + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.to_raw().into(), + 4, + 8, + log2_ceil(BlockState::max_raw().into()), + ) + .unwrap(); + + sect.biomes + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.0.into(), + 0, + 3, + log2_ceil(biome_registry_len), + ) + .unwrap(); } ChunkDataAndUpdateLight { diff --git a/src/chunk/paletted_container.rs b/src/chunk/paletted_container.rs index ba51607..5648019 100644 --- a/src/chunk/paletted_container.rs +++ b/src/chunk/paletted_container.rs @@ -9,39 +9,21 @@ use crate::util::log2_ceil; /// `HALF_LEN` must be equal to `ceil(LEN / 2)`. #[derive(Clone, Debug)] -pub enum PalettedContainer { +pub enum PalettedContainer { Single(T), Indirect(Box>), Direct(Box<[T; LEN]>), } -pub trait PalettedContainerElement: Copy + Eq + Default { - /// The minimum number of bits required to represent all instances of the - /// element type. If `N` is the total number of possible values, then - /// `DIRECT_BITS` is `ceil(log2(N))`. - const DIRECT_BITS: usize; - /// The maximum number of bits per element allowed in the indirect - /// representation while encoding. Any higher than this will force - /// conversion to the direct representation while encoding. - const MAX_INDIRECT_BITS: usize; - /// The minimum number of bits used to represent the element type in the - /// indirect representation while encoding. If the bits per index is lower, - /// it will be rounded up to this. - const MIN_INDIRECT_BITS: usize; - /// Converts the element type to bits. The output must be less than two to - /// the power of `DIRECT_BITS`. - fn to_bits(self) -> u64; -} - #[derive(Clone, Debug)] -pub struct Indirect { +pub struct Indirect { /// Each element is a unique instance of `T`. palette: ArrayVec, /// Each half-byte is an index into `palette`. indices: [u8; HALF_LEN], } -impl +impl PalettedContainer { pub fn new() -> Self { @@ -150,9 +132,108 @@ impl "index {idx} is out of bounds in paletted container of length {LEN}" ); } + + /// Encodes the paletted container in the format that Minecraft expects. + /// + /// - **`writer`**: The [`Write`] instance to write the paletted container + /// to. + /// - **`to_bits`**: A function to convert the element type to bits. The + /// output must be less than two to the power of `direct_bits`. + /// - **`min_indirect_bits`**: The minimum number of bits used to represent + /// the element type in the indirect representation. If the bits per index + /// is lower, it will be rounded up to this. + /// - **`max_indirect_bits`**: The maximum number of bits per element + /// allowed in the indirect representation. Any higher than this will + /// force conversion to the direct representation while encoding. + /// - **`direct_bits`**: The minimum number of bits required to represent + /// all instances of the element type. If `N` is the total number of + /// possible values, then `DIRECT_BITS` is `ceil(log2(N))`. + pub fn encode_mc_format( + &self, + mut writer: W, + mut to_bits: F, + min_indirect_bits: usize, + max_indirect_bits: usize, + direct_bits: usize, + ) -> anyhow::Result<()> + where + W: Write, + F: FnMut(T) -> u64, + { + debug_assert!(min_indirect_bits <= 4); + debug_assert!(min_indirect_bits <= max_indirect_bits); + debug_assert!(max_indirect_bits <= 64); + debug_assert!(direct_bits <= 64); + + match self { + Self::Single(val) => { + // Bits per entry + 0_u8.encode(&mut writer)?; + + // Palette + VarInt(to_bits(*val) as i32).encode(&mut writer)?; + + // Number of longs + VarInt(0).encode(&mut writer)?; + } + Self::Indirect(ind) => { + let bits_per_entry = min_indirect_bits.max(log2_ceil(ind.palette.len())); + + // Encode as direct if necessary. + if bits_per_entry > max_indirect_bits { + // Bits per entry + (direct_bits as u8).encode(&mut writer)?; + + // Number of longs in data array. + VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?; + // Data array + encode_compact_u64s( + &mut writer, + (0..LEN).map(|i| to_bits(ind.get(i))), + direct_bits, + )?; + } else { + // Bits per entry + (bits_per_entry as u8).encode(&mut writer)?; + + // Palette len + VarInt(ind.palette.len() as i32).encode(&mut writer)?; + // Palette + for val in &ind.palette { + VarInt(to_bits(*val) as i32).encode(&mut writer)?; + } + + // Number of longs in data array. + VarInt(compact_u64s_len(LEN, bits_per_entry) as _).encode(&mut writer)?; + // Data array + encode_compact_u64s( + &mut writer, + ind.indices + .iter() + .cloned() + .flat_map(|byte| [byte & 0b1111, byte >> 4]) + .map(u64::from) + .take(LEN), + bits_per_entry, + )?; + } + } + Self::Direct(dir) => { + // Bits per entry + (direct_bits as u8).encode(&mut writer)?; + + // Number of longs in data array. + VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?; + // Data array + encode_compact_u64s(&mut writer, dir.iter().cloned().map(to_bits), direct_bits)?; + } + } + + Ok(()) + } } -impl Default +impl Default for PalettedContainer { fn default() -> Self { @@ -160,9 +241,7 @@ impl Defau } } -impl - Indirect -{ +impl Indirect { pub fn get(&self, idx: usize) -> T { let palette_idx = self.indices[idx / 2] >> (idx % 2 * 4) & 0b1111; self.palette[palette_idx as usize] @@ -184,79 +263,13 @@ impl } } -/// Encodes the paletted container in the format that Minecraft expects. -impl Encode - for PalettedContainer -{ - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - assert!(T::DIRECT_BITS <= 64); - assert!(T::MAX_INDIRECT_BITS <= 64); - assert!(T::MIN_INDIRECT_BITS <= T::MAX_INDIRECT_BITS); - assert!(T::MIN_INDIRECT_BITS <= 4); - - match self { - Self::Single(val) => { - // Bits per entry - 0_u8.encode(w)?; - - // Palette - VarInt(val.to_bits() as i32).encode(w)?; - - // Number of longs - VarInt(0).encode(w)?; - } - Self::Indirect(ind) => { - let bits_per_entry = T::MIN_INDIRECT_BITS.max(log2_ceil(ind.palette.len())); - - // TODO: if bits_per_entry > MAX_INDIRECT_BITS, encode as direct. - debug_assert!(bits_per_entry <= T::MAX_INDIRECT_BITS); - - // Bits per entry - (bits_per_entry as u8).encode(w)?; - - // Palette len - VarInt(ind.palette.len() as i32).encode(w)?; - // Palette - for val in &ind.palette { - VarInt(val.to_bits() as i32).encode(w)?; - } - - // Number of longs in data array. - VarInt(compact_u64s_len(LEN, bits_per_entry) as _).encode(w)?; - // Data array - encode_compact_u64s( - w, - ind.indices - .iter() - .cloned() - .flat_map(|byte| [byte & 0b1111, byte >> 4]) - .map(u64::from) - .take(LEN), - bits_per_entry, - )?; - } - Self::Direct(dir) => { - // Bits per entry - (T::DIRECT_BITS as u8).encode(w)?; - - // Number of longs in data array. - VarInt(compact_u64s_len(LEN, T::DIRECT_BITS) as _).encode(w)?; - // Data array - encode_compact_u64s(w, dir.iter().map(|v| v.to_bits()), T::DIRECT_BITS)?; - } - } - - Ok(()) - } -} - #[cfg(test)] mod tests { use rand::Rng; use super::*; - fn check( + fn check( p: &PalettedContainer, s: &[T], ) -> bool { @@ -264,16 +277,6 @@ mod tests { (0..LEN).all(|i| p.get(i) == s[i]) } - impl PalettedContainerElement for u32 { - const DIRECT_BITS: usize = 0; - const MAX_INDIRECT_BITS: usize = 0; - const MIN_INDIRECT_BITS: usize = 0; - - fn to_bits(self) -> u64 { - self.into() - } - } - #[test] fn random_assignments() { const LEN: usize = 100; diff --git a/src/client.rs b/src/client.rs index f6928e5..22e23ce 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1138,7 +1138,7 @@ impl Client { registry_codec: shared.registry_codec().clone(), dimension_type_name: world.meta.dimension().dimension_type_name(), dimension_name: world.meta.dimension().dimension_name(), - hashed_seed: 0, + hashed_seed: 10, max_players: VarInt(0), view_distance: BoundedInt(VarInt(self.view_distance() as i32)), simulation_distance: VarInt(16), @@ -1332,7 +1332,7 @@ impl Client { for pos in chunks_in_view_distance(center, self.view_distance) { if let Some(chunk) = world.chunks.get(pos) { if self.loaded_chunks.insert(pos) { - self.send_packet(chunk.chunk_data_packet(pos)); + self.send_packet(chunk.chunk_data_packet(pos, shared.biomes().len())); } } } diff --git a/src/config.rs b/src/config.rs index 44e20a0..d54af04 100644 --- a/src/config.rs +++ b/src/config.rs @@ -176,6 +176,10 @@ pub trait Config: Sized + Send + Sync + 'static { /// Additionally, the documented requirements on the fields of [`Biome`] /// must be met. /// + /// **NOTE**: As of 1.19.2, there is a bug in the client which prevents + /// joining the game when a biome named "minecraft:plains" is not present. + /// Ensure there is a biome named "plains". + /// /// # Default Implementation /// /// Returns `vec![Biome::default()]`. diff --git a/src/server.rs b/src/server.rs index 9967ddb..8ce5919 100644 --- a/src/server.rs +++ b/src/server.rs @@ -393,24 +393,13 @@ fn make_registry_codec(dimensions: &[Dimension], biomes: &[Biome]) -> Compound { ident!("worldgen/biome") => compound! { "type" => ident!("worldgen/biome"), "value" => { - let mut biomes: Vec<_> = biomes + List::Compound(biomes .iter() .enumerate() .map(|(id, biome)| biome.to_biome_registry_item(id as i32)) - .collect(); - - // The client needs a biome named "minecraft:plains" in the registry to - // connect. This is probably a bug in the client. - // - // If the issue is resolved, remove this if. - if !biomes.iter().any(|b| b["name"] == "plains".into()) { - let biome = Biome::default(); - assert_eq!(biome.name, ident!("plains")); - biomes.push(biome.to_biome_registry_item(biomes.len() as i32)); - } - - List::Compound(biomes) + .collect()) } + }, ident!("chat_type_registry") => compound! { "type" => ident!("chat_type"),