aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-06-02 07:45:26 +1100
committermat <git@matdoes.dev>2025-06-02 07:45:26 +1100
commitd028d7c3e9c84d177b7b10fa0d8f77d11bcea20f (patch)
treea37fa4167a3171dd46c17d8ea5b8674cc72c3c78
parentb103e6fdc0daa131d1177c5d0705134640aa9d6e (diff)
downloadazalea-drasl-d028d7c3e9c84d177b7b10fa0d8f77d11bcea20f.tar.xz
add basic support for getting biome ids in chunks
-rw-r--r--azalea-block/src/block_state.rs6
-rw-r--r--azalea-chat/src/text_component.rs2
-rw-r--r--azalea-client/src/test_simulation.rs12
-rw-r--r--azalea-core/src/position.rs48
-rw-r--r--azalea-physics/src/collision/world_collisions.rs6
-rw-r--r--azalea-registry/src/data.rs18
-rw-r--r--azalea-world/benches/chunks.rs2
-rw-r--r--azalea-world/src/chunk_storage.rs115
-rw-r--r--azalea-world/src/find_blocks.rs20
-rw-r--r--azalea-world/src/palette.rs432
-rw-r--r--azalea-world/src/palette/container.rs314
-rw-r--r--azalea-world/src/palette/mod.rs115
-rw-r--r--azalea-world/src/palette/tests.rs80
-rw-r--r--azalea-world/src/world.rs5
-rw-r--r--azalea/src/pathfinder/world.rs19
15 files changed, 689 insertions, 505 deletions
diff --git a/azalea-block/src/block_state.rs b/azalea-block/src/block_state.rs
index ab0ca076..dfa2a9b2 100644
--- a/azalea-block/src/block_state.rs
+++ b/azalea-block/src/block_state.rs
@@ -94,6 +94,12 @@ impl TryFrom<u16> for BlockState {
}
}
}
+impl From<BlockState> for u32 {
+ /// See [`BlockState::id`].
+ fn from(value: BlockState) -> Self {
+ value.id as u32
+ }
+}
impl AzaleaRead for BlockState {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs
index 228dad3b..bd598e16 100644
--- a/azalea-chat/src/text_component.rs
+++ b/azalea-chat/src/text_component.rs
@@ -193,7 +193,7 @@ mod tests {
END_SPAN = "</span>",
GREEN = "<span style=\"color:#55FF55;\">",
RED = "<span style=\"color:#FF5555;\">",
- BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight: bold;\">",
+ BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight:bold;\">",
)
);
}
diff --git a/azalea-client/src/test_simulation.rs b/azalea-client/src/test_simulation.rs
index c9f28e01..3f7cd8b2 100644
--- a/azalea-client/src/test_simulation.rs
+++ b/azalea-client/src/test_simulation.rs
@@ -1,6 +1,7 @@
use std::{fmt::Debug, sync::Arc};
use azalea_auth::game_profile::GameProfile;
+use azalea_block::BlockState;
use azalea_buf::AzaleaWrite;
use azalea_core::{
delta::PositionDelta8,
@@ -20,11 +21,8 @@ use azalea_protocol::packets::{
c_light_update::ClientboundLightUpdatePacketData,
},
};
-use azalea_registry::{DimensionType, EntityKind};
-use azalea_world::{
- Chunk, Instance, MinecraftEntityId, Section,
- palette::{PalettedContainer, PalettedContainerKind},
-};
+use azalea_registry::{Biome, DimensionType, EntityKind};
+use azalea_world::{Chunk, Instance, MinecraftEntityId, Section, palette::PalettedContainer};
use bevy_app::App;
use bevy_ecs::{component::Mutable, prelude::*, schedule::ExecutorKind};
use parking_lot::RwLock;
@@ -261,8 +259,8 @@ pub fn make_basic_empty_chunk(
for _ in 0..section_count {
sections.push(Section {
block_count: 0,
- states: PalettedContainer::new(PalettedContainerKind::BlockStates),
- biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
+ states: PalettedContainer::<BlockState>::new(),
+ biomes: PalettedContainer::<Biome>::new(),
});
}
sections.azalea_write(&mut chunk_bytes).unwrap();
diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs
index 04cce036..5932cb5b 100644
--- a/azalea-core/src/position.rs
+++ b/azalea-core/src/position.rs
@@ -539,8 +539,8 @@ impl From<ChunkBlockPos> for u64 {
}
impl nohash_hasher::IsEnabled for ChunkBlockPos {}
-/// The coordinates of a block inside a chunk section. Each coordinate must be
-/// in the range [0, 15].
+/// The coordinates of a block inside a chunk section. Each coordinate should be
+/// in the range 0..=15.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChunkSectionBlockPos {
pub x: u8,
@@ -549,6 +549,50 @@ pub struct ChunkSectionBlockPos {
}
vec3_impl!(ChunkSectionBlockPos, u8);
+/// The coordinates of a chunk inside a chunk section. Each coordinate should be
+/// in the range 0..=3.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct ChunkSectionBiomePos {
+ pub x: u8,
+ pub y: u8,
+ pub z: u8,
+}
+impl From<&ChunkBiomePos> for ChunkSectionBiomePos {
+ #[inline]
+ fn from(pos: &ChunkBiomePos) -> Self {
+ ChunkSectionBiomePos {
+ x: pos.x,
+ y: (pos.y & 0b11) as u8,
+ z: pos.z,
+ }
+ }
+}
+vec3_impl!(ChunkSectionBiomePos, u8);
+
+/// The coordinates of a biome inside a chunk. Biomes are 4x4 blocks.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct ChunkBiomePos {
+ pub x: u8,
+ pub y: i32,
+ pub z: u8,
+}
+impl From<&BlockPos> for ChunkBiomePos {
+ #[inline]
+ fn from(pos: &BlockPos) -> Self {
+ ChunkBiomePos::from(&ChunkBlockPos::from(pos))
+ }
+}
+impl From<&ChunkBlockPos> for ChunkBiomePos {
+ #[inline]
+ fn from(pos: &ChunkBlockPos) -> Self {
+ ChunkBiomePos {
+ x: pos.x >> 2,
+ y: pos.y >> 2,
+ z: pos.z >> 2,
+ }
+ }
+}
+
impl Add<ChunkSectionBlockPos> for ChunkSectionPos {
type Output = BlockPos;
diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs
index ded31275..11e8946f 100644
--- a/azalea-physics/src/collision/world_collisions.rs
+++ b/azalea-physics/src/collision/world_collisions.rs
@@ -86,7 +86,7 @@ impl<'a> BlockCollisionsState<'a> {
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
match &initial_chunk {
Some(initial_chunk) => initial_chunk
- .get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
+ .get_block_state(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
.unwrap_or(BlockState::AIR),
_ => BlockState::AIR,
}
@@ -186,7 +186,7 @@ impl<'a> BlockCollisionsState<'a> {
for (cached_section_pos, cached_section) in &self.cached_sections {
if section_pos == *cached_section_pos {
- return cached_section.get(section_block_pos);
+ return cached_section.get_block_state(section_block_pos);
}
}
@@ -211,7 +211,7 @@ impl<'a> BlockCollisionsState<'a> {
// println!("chunk section data: {:?}", section.states.storage.data);
// println!("biome length: {}", section.biomes.storage.data.len());
- section.get(section_block_pos)
+ section.get_block_state(section_block_pos)
}
fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape {
diff --git a/azalea-registry/src/data.rs b/azalea-registry/src/data.rs
index 7ae55119..91e7106f 100644
--- a/azalea-registry/src/data.rs
+++ b/azalea-registry/src/data.rs
@@ -53,3 +53,21 @@ data_registry! {CatVariant, "cat_variant"}
data_registry! {PigVariant, "pig_variant"}
data_registry! {PaintingVariant, "painting_variant"}
data_registry! {WolfVariant, "wolf_variant"}
+
+data_registry! {Biome, "biome"}
+// these extra traits are required for Biome to be allowed to be palletable
+impl Default for Biome {
+ fn default() -> Self {
+ Self::new_raw(0)
+ }
+}
+impl From<u32> for Biome {
+ fn from(id: u32) -> Self {
+ Self::new_raw(id)
+ }
+}
+impl From<Biome> for u32 {
+ fn from(biome: Biome) -> Self {
+ biome.protocol_id()
+ }
+}
diff --git a/azalea-world/benches/chunks.rs b/azalea-world/benches/chunks.rs
index 0e7f4554..f84fdc56 100644
--- a/azalea-world/benches/chunks.rs
+++ b/azalea-world/benches/chunks.rs
@@ -11,7 +11,7 @@ fn bench_chunks(c: &mut Criterion) {
for x in 0..16 {
for z in 0..16 {
- chunk.set(
+ chunk.set_block_state(
&ChunkBlockPos::new(x, 1, z),
azalea_registry::Block::Bedrock.into(),
0,
diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs
index b65c86b1..23d1bb89 100644
--- a/azalea-world/src/chunk_storage.rs
+++ b/azalea-world/src/chunk_storage.rs
@@ -12,14 +12,17 @@ use azalea_block::{
fluid_state::FluidState,
};
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
-use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
+use azalea_core::position::{
+ BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos, ChunkSectionBiomePos, ChunkSectionBlockPos,
+};
+use azalea_registry::Biome;
use nohash_hasher::IntMap;
use parking_lot::RwLock;
use tracing::{debug, trace, warn};
use crate::{
heightmap::{Heightmap, HeightmapKind},
- palette::{PalettedContainer, PalettedContainerKind},
+ palette::PalettedContainer,
};
const SECTION_HEIGHT: u32 = 16;
@@ -59,11 +62,11 @@ pub struct Chunk {
}
/// A section of a chunk, i.e. a 16*16*16 block area.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
pub struct Section {
pub block_count: u16,
- pub states: PalettedContainer,
- pub biomes: PalettedContainer,
+ pub states: PalettedContainer<BlockState>,
+ pub biomes: PalettedContainer<Biome>,
}
/// Get the actual stored view distance for the selected view distance. For some
@@ -72,16 +75,6 @@ pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 {
u32::max(view_distance, 2) + 3
}
-impl Default for Section {
- fn default() -> Self {
- Section {
- block_count: 0,
- states: PalettedContainer::new(PalettedContainerKind::BlockStates),
- biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
- }
- }
-}
-
impl Default for Chunk {
fn default() -> Self {
Chunk {
@@ -171,7 +164,7 @@ impl PartialChunkStorage {
let chunk_pos = ChunkPos::from(pos);
let chunk_lock = chunk_storage.get(&chunk_pos)?;
let mut chunk = chunk_lock.write();
- Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
+ Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
}
pub fn replace_with_packet_data(
@@ -301,7 +294,7 @@ impl ChunkStorage {
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let chunk = chunk.read();
- chunk.get(&ChunkBlockPos::from(pos), self.min_y)
+ chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y)
}
pub fn get_fluid_state(&self, pos: &BlockPos) -> Option<FluidState> {
@@ -309,6 +302,13 @@ impl ChunkStorage {
Some(FluidState::from(block_state))
}
+ pub fn get_biome(&self, pos: &BlockPos) -> Option<Biome> {
+ let chunk_pos = ChunkPos::from(pos);
+ let chunk = self.get(&chunk_pos)?;
+ let chunk = chunk.read();
+ chunk.get_biome(&ChunkBiomePos::from(pos), self.min_y)
+ }
+
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) {
return None;
@@ -316,7 +316,7 @@ impl ChunkStorage {
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let mut chunk = chunk.write();
- Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y))
+ Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y))
}
}
@@ -346,7 +346,7 @@ impl Chunk {
let mut heightmaps = HashMap::new();
for (kind, data) in heightmaps_data {
- let data: Box<[u64]> = data.iter().copied().collect();
+ let data: Box<[u64]> = data.clone();
let heightmap = Heightmap::new(*kind, dimension_height, min_y, data);
heightmaps.insert(*kind, heightmap);
}
@@ -357,22 +357,26 @@ impl Chunk {
})
}
- pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
+ pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
get_block_state_from_sections(&self.sections, pos, min_y)
}
- #[must_use = "Use Chunk::set instead if you don't need the previous state"]
- pub fn get_and_set(
+ #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"]
+ pub fn get_and_set_block_state(
&mut self,
pos: &ChunkBlockPos,
state: BlockState,
min_y: i32,
) -> BlockState {
let section_index = section_index(pos.y, min_y);
- // TODO: make sure the section exists
- let section = &mut self.sections[section_index as usize];
+ let Some(section) = self.sections.get_mut(section_index as usize) else {
+ warn!(
+ "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
+ );
+ return BlockState::AIR;
+ };
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
- let previous_state = section.get_and_set(chunk_section_pos, state);
+ let previous_state = section.get_and_set_block_state(chunk_section_pos, state);
for heightmap in self.heightmaps.values_mut() {
heightmap.update(pos, state, &self.sections);
@@ -381,17 +385,35 @@ impl Chunk {
previous_state
}
- pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
+ pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
let section_index = section_index(pos.y, min_y);
- // TODO: make sure the section exists
- let section = &mut self.sections[section_index as usize];
+ let Some(section) = self.sections.get_mut(section_index as usize) else {
+ warn!(
+ "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
+ );
+ return;
+ };
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
- section.set(chunk_section_pos, state);
+ section.set_block_state(chunk_section_pos, state);
for heightmap in self.heightmaps.values_mut() {
heightmap.update(pos, state, &self.sections);
}
}
+
+ pub fn get_biome(&self, pos: &ChunkBiomePos, min_y: i32) -> Option<Biome> {
+ if pos.y < min_y {
+ // y position is out of bounds
+ return None;
+ }
+ let section_index = section_index(pos.y, min_y);
+ let Some(section) = self.sections.get(section_index as usize) else {
+ warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",);
+ return None;
+ };
+ let chunk_section_pos = ChunkSectionBiomePos::from(pos);
+ Some(section.get_biome(chunk_section_pos))
+ }
}
/// Get the block state at the given position from a list of sections. Returns
@@ -413,7 +435,7 @@ pub fn get_block_state_from_sections(
};
let section = &sections[section_index];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
- Some(section.get(chunk_section_pos))
+ Some(section.get_block_state(chunk_section_pos))
}
impl AzaleaWrite for Chunk {
@@ -448,7 +470,7 @@ impl AzaleaRead for Section {
// "A section has more blocks than what should be possible. This is a bug!"
// );
- let states = PalettedContainer::read_with_type(buf, &PalettedContainerKind::BlockStates)?;
+ let states = PalettedContainer::<BlockState>::read(buf)?;
for i in 0..states.storage.size() {
if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) {
@@ -459,7 +481,7 @@ impl AzaleaRead for Section {
}
}
- let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerKind::Biomes)?;
+ let biomes = PalettedContainer::<Biome>::read(buf)?;
Ok(Section {
block_count,
states,
@@ -478,19 +500,28 @@ impl AzaleaWrite for Section {
}
impl Section {
- pub fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
- self.states
- .get(pos.x as usize, pos.y as usize, pos.z as usize)
+ pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState {
+ self.states.get(pos)
}
-
- pub fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState {
- self.states
- .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state)
+ pub fn get_and_set_block_state(
+ &mut self,
+ pos: ChunkSectionBlockPos,
+ state: BlockState,
+ ) -> BlockState {
+ self.states.get_and_set(pos, state)
+ }
+ pub fn set_block_state(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
+ self.states.set(pos, state);
}
- pub fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
- self.states
- .set(pos.x as usize, pos.y as usize, pos.z as usize, state);
+ pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome {
+ self.biomes.get(pos)
+ }
+ pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) {
+ self.biomes.set(pos, biome);
+ }
+ pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome {
+ self.biomes.get_and_set(pos, biome)
}
}
diff --git a/azalea-world/src/find_blocks.rs b/azalea-world/src/find_blocks.rs
index 1215549b..b266d799 100644
--- a/azalea-world/src/find_blocks.rs
+++ b/azalea-world/src/find_blocks.rs
@@ -1,9 +1,9 @@
-use azalea_block::BlockStates;
+use azalea_block::{BlockState, BlockStates};
use azalea_core::position::{BlockPos, ChunkPos};
use crate::{ChunkStorage, Instance, iterators::ChunkIterator, palette::Palette};
-fn palette_maybe_has_block(palette: &Palette, block_states: &BlockStates) -> bool {
+fn palette_maybe_has_block(palette: &Palette<BlockState>, block_states: &BlockStates) -> bool {
match &palette {
Palette::SingleValue(id) => block_states.contains(id),
Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)),
@@ -63,11 +63,11 @@ impl Instance {
let block_state = section.states.get_at_index(i);
if block_states.contains(&block_state) {
- let (section_x, section_y, section_z) = section.states.coords_from_index(i);
+ let section_pos = section.states.coords_from_index(i);
let (x, y, z) = (
- chunk_pos.x * 16 + (section_x as i32),
- self.chunks.min_y + (section_index * 16) as i32 + section_y as i32,
- chunk_pos.z * 16 + (section_z as i32),
+ chunk_pos.x * 16 + (section_pos.x as i32),
+ self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32,
+ chunk_pos.z * 16 + (section_pos.z as i32),
);
let this_block_pos = BlockPos { x, y, z };
let this_block_distance = (nearest_to - this_block_pos).length_manhattan();
@@ -190,11 +190,11 @@ impl Iterator for FindBlocks<'_> {
let block_state = section.states.get_at_index(i);
if self.block_states.contains(&block_state) {
- let (section_x, section_y, section_z) = section.states.coords_from_index(i);
+ let section_pos = section.states.coords_from_index(i);
let (x, y, z) = (
- chunk_pos.x * 16 + (section_x as i32),
- self.chunks.min_y + (section_index * 16) as i32 + section_y as i32,
- chunk_pos.z * 16 + (section_z as i32),
+ chunk_pos.x * 16 + (section_pos.x as i32),
+ self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32,
+ chunk_pos.z * 16 + (section_pos.z as i32),
);
let this_block_pos = BlockPos { x, y, z };
let this_block_distance =
diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs
deleted file mode 100644
index cef9bdee..00000000
--- a/azalea-world/src/palette.rs
+++ /dev/null
@@ -1,432 +0,0 @@
-use std::io::{self, Cursor, Write};
-
-use azalea_block::BlockState;
-use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
-use tracing::warn;
-
-use crate::BitStorage;
-
-#[derive(Clone, Debug, Copy)]
-pub enum PalettedContainerKind {
- Biomes,
- BlockStates,
-}
-
-#[derive(Clone, Debug)]
-pub struct PalettedContainer {
- pub bits_per_entry: u8,
- /// This is usually a list of unique values that appear in the container so
- /// they can be indexed by the bit storage.
- ///
- /// Sometimes it doesn't contain anything if there's too many unique items
- /// in the bit storage, though.
- pub palette: Palette,
- /// Compacted list of indices pointing to entry IDs in the Palette.
- pub storage: BitStorage,
- pub container_type: PalettedContainerKind,
-}
-
-impl PalettedContainer {
- pub fn new(container_type: PalettedContainerKind) -> Self {
- let palette = Palette::SingleValue(BlockState::AIR);
- let size = container_type.size();
- let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap();
-
- PalettedContainer {
- bits_per_entry: 0,
- palette,
- storage,
- container_type,
- }
- }
-
- pub fn read_with_type(
- buf: &mut Cursor<&[u8]>,
- container_type: &'static PalettedContainerKind,
- ) -> Result<Self, BufReadError> {
- let bits_per_entry = u8::azalea_read(buf)?;
- let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type);
- let palette = palette_type.read(buf)?;
- let size = container_type.size();
-
- let mut storage = match BitStorage::new(
- bits_per_entry as usize,
- size,
- if bits_per_entry == 0 {
- Some(Box::new([]))
- } else {
- // we're going to update the data after creating the bitstorage
- None
- },
- ) {
- Ok(storage) => storage,
- Err(e) => {
- warn!("Failed to create bit storage: {:?}", e);
- return Err(BufReadError::Custom(
- "Failed to create bit storage".to_string(),
- ));
- }
- };
-
- // now read the data
- for i in 0..storage.data.len() {
- storage.data[i] = u64::azalea_read(buf)?;
- }
-
- Ok(PalettedContainer {
- bits_per_entry,
- palette,
- storage,
- container_type: *container_type,
- })
- }
-
- /// Calculates the index of the given coordinates.
- pub fn index_from_coords(&self, x: usize, y: usize, z: usize) -> usize {
- let size_bits = self.container_type.size_bits();
-
- (((y << size_bits) | z) << size_bits) | x
- }
-
- pub fn coords_from_index(&self, index: usize) -> (usize, usize, usize) {
- let size_bits = self.container_type.size_bits();
- let mask = (1 << size_bits) - 1;
- (
- index & mask,
- (index >> size_bits >> size_bits) & mask,
- (index >> size_bits) & mask,
- )
- }
-
- /// Returns the value at the given index.
- ///
- /// # Panics
- ///
- /// This function panics if the index is greater than or equal to the number
- /// of things in the storage. (So for block states, it must be less than
- /// 4096).
- pub fn get_at_index(&self, index: usize) -> BlockState {
- // first get the palette id
- let paletted_value = self.storage.get(index);
- // and then get the value from that id
- self.palette.value_for(paletted_value as usize)
- }
-
- /// Returns the value at the given coordinates.
- pub fn get(&self, x: usize, y: usize, z: usize) -> BlockState {
- // let paletted_value = self.storage.get(self.get_index(x, y, z));
- // self.palette.value_for(paletted_value as usize)
- self.get_at_index(self.index_from_coords(x, y, z))
- }
-
- /// Sets the id at the given coordinates and return the previous id
- pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: BlockState) -> BlockState {
- let paletted_value = self.id_for(value);
- let block_state_id = self
- .storage
- .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64);
- // error in debug mode
- #[cfg(debug_assertions)]
- if block_state_id > BlockState::MAX_STATE.into() {
- warn!(
- "Old block state from get_and_set {block_state_id} was greater than max state {}",
- BlockState::MAX_STATE
- );
- }
-
- BlockState::try_from(block_state_id as u32).unwrap_or_default()
- }
-
- /// Sets the id at the given index and return the previous id. You probably
- /// want `.set` instead.
- pub fn set_at_index(&mut self, index: usize, value: BlockState) {
- let paletted_value = self.id_for(value);
- self.storage.set(index, paletted_value as u64);
- }
-
- /// Sets the id at the given coordinates and return the previous id
- pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockState) {
- self.set_at_index(self.index_from_coords(x, y, z), value);
- }
-
- fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer {
- let new_palette_type =
- PaletteKind::from_bits_and_type(bits_per_entry, &self.container_type);
-
- let old_palette_type = (&self.palette).into();
- if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type {
- return self.clone();
- }
- let storage =
- BitStorage::new(bits_per_entry as usize, self.container_type.size(), None).unwrap();
-
- // sanity check
- debug_assert_eq!(storage.size(), self.container_type.size());
-
- // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as
- // usize));
- let palette = new_palette_type.as_empty_palette();
- PalettedContainer {
- bits_per_entry,
- palette,
- storage,
- container_type: self.container_type,
- }
- }
-
- fn on_resize(&mut self, bits_per_entry: u8, value: BlockState) -> usize {
- // in vanilla this is always true, but it's sometimes false in purpur servers
- // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5");
- let mut new_data = self.create_or_reuse_data(bits_per_entry);
- new_data.copy_from(&self.palette, &self.storage);
- *self = new_data;
- self.id_for(value)
- }
-
- fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) {
- for i in 0..storage.size() {
- let value = palette.value_for(storage.get(i) as usize);
- let id = self.id_for(value) as u64;
- self.storage.set(i, id);
- }
- }
-
- pub fn id_for(&mut self, value: BlockState) -> usize {
- match &mut self.palette {
- Palette::SingleValue(v) => {
- if *v != value {
- self.on_resize(1, value)
- } else {
- 0
- }
- }
- Palette::Linear(palette) => {
- if let Some(index) = palette.iter().position(|&v| v == value) {
- return index;
- }
- let capacity = 2usize.pow(self.bits_per_entry.into());
- if capacity > palette.len() {
- palette.push(value);
- palette.len() - 1
- } else {
- self.on_resize(self.bits_per_entry + 1, value)
- }
- }
- Palette::Hashmap(palette) => {
- // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked
- // before changing it
- if let Some(index) = palette.iter().position(|v| *v == value) {
- return index;
- }
- let capacity = 2usize.pow(self.bits_per_entry.into());
- if capacity > palette.len() {
- palette.push(value);
- palette.len() - 1
- } else {
- self.on_resize(self.bits_per_entry + 1, value)
- }
- }
- Palette::Global => value.id() as usize,
- }
- }
-}
-
-impl AzaleaWrite for PalettedContainer {
- fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
- self.bits_per_entry.azalea_write(buf)?;
- self.palette.azalea_write(buf)?;
- self.storage.data.azalea_write(buf)?;
- Ok(())
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum PaletteKind {
- SingleValue,
- Linear,
- Hashmap,
- Global,
-}
-
-/// A representation of the different types of chunk palettes Minecraft uses.
-#[derive(Clone, Debug)]
-pub enum Palette {
- /// ID of the corresponding entry in its global palette
- SingleValue(BlockState),
- // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure
- // it's actually needed?
- Linear(Vec<BlockState>),
- Hashmap(Vec<BlockState>),
- Global,
-}
-
-impl Palette {
- pub fn value_for(&self, id: usize) -> BlockState {
- match self {
- Palette::SingleValue(v) => *v,
- Palette::Linear(v) => v.get(id).copied().unwrap_or_default(),
- Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(),
- Palette::Global => BlockState::try_from(id as u32).unwrap_or_default(),
- }
- }
-}
-
-impl AzaleaWrite for Palette {
- fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
- match self {
- Palette::SingleValue(value) => {
- value.azalea_write(buf)?;
- }
- Palette::Linear(values) => {
- values.azalea_write(buf)?;
- }
- Palette::Hashmap(values) => {
- values.azalea_write(buf)?;
- }
- Palette::Global => {}
- }
- Ok(())
- }
-}
-
-impl PaletteKind {
- pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerKind) -> Self {
- match container_type {
- PalettedContainerKind::BlockStates => match bits_per_entry {
- 0 => PaletteKind::SingleValue,
- 1..=4 => PaletteKind::Linear,
- 5..=8 => PaletteKind::Hashmap,
- _ => PaletteKind::Global,
- },
- PalettedContainerKind::Biomes => match bits_per_entry {
- 0 => PaletteKind::SingleValue,
- 1..=3 => PaletteKind::Linear,
- _ => PaletteKind::Global,
- },
- }
- }
-
- pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result<Palette, BufReadError> {
- Ok(match self {
- // since they're read as varints it's actually fine to just use BlockStateIntegerRepr
- // instead of the correct type (u32)
- PaletteKind::SingleValue => Palette::SingleValue(BlockState::azalea_read(buf)?),
- PaletteKind::Linear => Palette::Linear(Vec::<BlockState>::azalea_read(buf)?),
- PaletteKind::Hashmap => Palette::Hashmap(Vec::<BlockState>::azalea_read(buf)?),
- PaletteKind::Global => Palette::Global,
- })
- }
-
- pub fn as_empty_palette(&self) -> Palette {
- match self {
- PaletteKind::SingleValue => Palette::SingleValue(BlockState::AIR),
- PaletteKind::Linear => Palette::Linear(Vec::new()),
- PaletteKind::Hashmap => Palette::Hashmap(Vec::new()),
- PaletteKind::Global => Palette::Global,
- }
- }
-}
-
-impl From<&Palette> for PaletteKind {
- fn from(palette: &Palette) -> Self {
- match palette {
- Palette::SingleValue(_) => PaletteKind::SingleValue,
- Palette::Linear(_) => PaletteKind::Linear,
- Palette::Hashmap(_) => PaletteKind::Hashmap,
- Palette::Global => PaletteKind::Global,
- }
- }
-}
-
-impl PalettedContainerKind {
- fn size_bits(&self) -> usize {
- match self {
- PalettedContainerKind::BlockStates => 4,
- PalettedContainerKind::Biomes => 2,
- }
- }
-
- fn size(&self) -> usize {
- 1 << (self.size_bits() * 3)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_resize_0_bits_to_1() {
- let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates);
-
- assert_eq!(palette_container.bits_per_entry, 0);
- assert_eq!(palette_container.get_at_index(0), BlockState::AIR);
- assert_eq!(
- PaletteKind::from(&palette_container.palette),
- PaletteKind::SingleValue
- );
- let block_state_1 = BlockState::try_from(1_u32).unwrap();
- palette_container.set_at_index(0, block_state_1);
- assert_eq!(palette_container.get_at_index(0), block_state_1);
- assert_eq!(
- PaletteKind::from(&palette_container.palette),
- PaletteKind::Linear
- );
- }
-
- #[test]
- fn test_resize_0_bits_to_5() {
- let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates);
-
- let set = |pc: &mut PalettedContainer, i, v: u32| {
- pc.set_at_index(i, BlockState::try_from(v).unwrap());
- };
-
- set(&mut palette_container, 0, 0); // 0 bits
- assert_eq!(palette_container.bits_per_entry, 0);
-
- set(&mut palette_container, 1, 1); // 1 bit
- assert_eq!(palette_container.bits_per_entry, 1);
-
- set(&mut palette_container, 2, 2); // 2 bits
- assert_eq!(palette_container.bits_per_entry, 2);
- set(&mut palette_container, 3, 3);
-
- set(&mut palette_container, 4, 4); // 3 bits
- assert_eq!(palette_container.bits_per_entry, 3);
- set(&mut palette_container, 5, 5);
- set(&mut palette_container, 6, 6);
- set(&mut palette_container, 7, 7);
-
- set(&mut palette_container, 8, 8); // 4 bits
- assert_eq!(palette_container.bits_per_entry, 4);
- set(&mut palette_container, 9, 9);
- set(&mut palette_container, 10, 10);
- set(&mut palette_container, 11, 11);
- set(&mut palette_container, 12, 12);
- set(&mut palette_container, 13, 13);
- set(&mut palette_container, 14, 14);
- set(&mut palette_container, 15, 15);
- assert_eq!(palette_container.bits_per_entry, 4);
-
- set(&mut palette_container, 16, 16); // 5 bits
- assert_eq!(palette_container.bits_per_entry, 5);
- }
-
- #[test]
- fn test_coords_from_index() {
- let palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates);
-
- for x in 0..15 {
- for y in 0..15 {
- for z in 0..15 {
- assert_eq!(
- palette_container
- .coords_from_index(palette_container.index_from_coords(x, y, z)),
- (x, y, z)
- );
- }
- }
- }
- }
-}
diff --git a/azalea-world/src/palette/container.rs b/azalea-world/src/palette/container.rs
new file mode 100644
index 00000000..2f3d9238
--- /dev/null
+++ b/azalea-world/src/palette/container.rs
@@ -0,0 +1,314 @@
+use std::{
+ fmt::Debug,
+ io::{self, Cursor, Write},
+};
+
+use azalea_block::BlockState;
+use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
+use azalea_core::position::{ChunkSectionBiomePos, ChunkSectionBlockPos};
+use azalea_registry::Biome;
+use tracing::warn;
+
+use super::{Palette, PaletteKind};
+use crate::BitStorage;
+
+#[derive(Clone, Debug)]
+pub struct PalettedContainer<S: PalletedContainerKind> {
+ pub bits_per_entry: u8,
+ /// This is usually a list of unique values that appear in the container so
+ /// they can be indexed by the bit storage.
+ ///
+ /// Sometimes it doesn't contain anything if there's too many unique items
+ /// in the bit storage, though.
+ pub palette: Palette<S>,
+ /// Compacted list of indices pointing to entry IDs in the Palette.
+ pub storage: BitStorage,
+}
+
+pub trait PalletedContainerKind: Copy + Clone + Debug + Default + TryFrom<u32> + Into<u32> {
+ type SectionPos: SectionPos;
+
+ fn size_bits() -> usize;
+
+ fn size() -> usize {
+ 1 << (Self::size_bits() * 3)
+ }
+
+ fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind;
+}
+impl PalletedContainerKind for BlockState {
+ type SectionPos = ChunkSectionBlockPos;
+
+ fn size_bits() -> usize {
+ 4
+ }
+
+ fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind {
+ match bits_per_entry {
+ 0 => PaletteKind::SingleValue,
+ 1..=4 => PaletteKind::Linear,
+ 5..=8 => PaletteKind::Hashmap,
+ _ => PaletteKind::Global,
+ }
+ }
+}
+impl PalletedContainerKind for Biome {
+ type SectionPos = ChunkSectionBiomePos;
+
+ fn size_bits() -> usize {
+ 2
+ }
+
+ fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind {
+ match bits_per_entry {
+ 0 => PaletteKind::SingleValue,
+ 1..=3 => PaletteKind::Linear,
+ _ => PaletteKind::Global,
+ }
+ }
+}
+
+/// A trait for position types that are sometimes valid ways to index into a
+/// chunk section.
+pub trait SectionPos {
+ fn coords(&self) -> (usize, usize, usize);
+ fn new(x: usize, y: usize, z: usize) -> Self;
+}
+impl SectionPos for ChunkSectionBlockPos {
+ fn coords(&self) -> (usize, usize, usize) {
+ (self.x as usize, self.y as usize, self.z as usize)
+ }
+
+ fn new(x: usize, y: usize, z: usize) -> Self {
+ ChunkSectionBlockPos {
+ x: x as u8,
+ y: y as u8,
+ z: z as u8,
+ }
+ }
+}
+impl SectionPos for ChunkSectionBiomePos {
+ fn coords(&self) -> (usize, usize, usize) {
+ (self.x as usize, self.y as usize, self.z as usize)
+ }
+
+ fn new(x: usize, y: usize, z: usize) -> Self {
+ ChunkSectionBiomePos {
+ x: x as u8,
+ y: y as u8,
+ z: z as u8,
+ }
+ }
+}
+
+impl<S: PalletedContainerKind> PalettedContainer<S> {
+ pub fn new() -> Self {
+ let palette = Palette::SingleValue(S::default());
+ let size = S::size();
+ let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap();
+
+ PalettedContainer {
+ bits_per_entry: 0,
+ palette,
+ storage,
+ }
+ }
+
+ pub fn read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
+ let bits_per_entry = u8::azalea_read(buf)?;
+ let palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry);
+ let palette = palette_type.read(buf)?;
+ let size = S::size();
+
+ let mut storage = match BitStorage::new(
+ bits_per_entry as usize,
+ size,
+ if bits_per_entry == 0 {
+ Some(Box::new([]))
+ } else {
+ // we're going to update the data after creating the bitstorage
+ None
+ },
+ ) {
+ Ok(storage) => storage,
+ Err(e) => {
+ warn!("Failed to create bit storage: {:?}", e);
+ return Err(BufReadError::Custom(
+ "Failed to create bit storage".to_string(),
+ ));
+ }
+ };
+
+ // now read the data
+ for i in 0..storage.data.len() {
+ storage.data[i] = u64::azalea_read(buf)?;
+ }
+
+ Ok(PalettedContainer {
+ bits_per_entry,
+ palette,
+ storage,
+ })
+ }
+
+ /// Calculates the index of the given coordinates.
+ pub fn index_from_coords(&self, pos: S::SectionPos) -> usize {
+ let size_bits = S::size_bits();
+ let (x, y, z) = pos.coords();
+ (((y << size_bits) | z) << size_bits) | x
+ }
+
+ pub fn coords_from_index(&self, index: usize) -> S::SectionPos {
+ let size_bits = S::size_bits();
+ let mask = (1 << size_bits) - 1;
+ S::SectionPos::new(
+ index & mask,
+ (index >> size_bits >> size_bits) & mask,
+ (index >> size_bits) & mask,
+ )
+ }
+
+ /// Returns the value at the given index.
+ ///
+ /// # Panics
+ ///
+ /// This function panics if the index is greater than or equal to the number
+ /// of things in the storage. (So for block states, it must be less than
+ /// 4096).
+ pub fn get_at_index(&self, index: usize) -> S {
+ // first get the palette id
+ let paletted_value = self.storage.get(index);
+ // and then get the value from that id
+ self.palette.value_for(paletted_value as usize)
+ }
+
+ /// Returns the value at the given coordinates.
+ pub fn get(&self, pos: S::SectionPos) -> S {
+ // let paletted_value = self.storage.get(self.get_index(x, y, z));
+ // self.palette.value_for(paletted_value as usize)
+ self.get_at_index(self.index_from_coords(pos))
+ }
+
+ /// Sets the id at the given coordinates and return the previous id
+ pub fn get_and_set(&mut self, pos: S::SectionPos, value: S) -> S {
+ let paletted_value = self.id_for(value);
+ let block_state_id = self
+ .storage
+ .get_and_set(self.index_from_coords(pos), paletted_value as u64);
+ // error in debug mode
+ #[cfg(debug_assertions)]
+ if block_state_id > BlockState::MAX_STATE.into() {
+ warn!(
+ "Old block state from get_and_set {block_state_id} was greater than max state {}",
+ BlockState::MAX_STATE
+ );
+ }
+
+ S::try_from(block_state_id as u32).unwrap_or_default()
+ }
+
+ /// Sets the id at the given index and return the previous id. You probably
+ /// want `.set` instead.
+ pub fn set_at_index(&mut self, index: usize, value: S) {
+ let paletted_value = self.id_for(value);
+ self.storage.set(index, paletted_value as u64);
+ }
+
+ /// Sets the id at the given coordinates and return the previous id
+ pub fn set(&mut self, pos: S::SectionPos, value: S) {
+ self.set_at_index(self.index_from_coords(pos), value);
+ }
+
+ fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer<S> {
+ let new_palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry);
+
+ let old_palette_type = (&self.palette).into();
+ if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type {
+ return self.clone();
+ }
+ let storage = BitStorage::new(bits_per_entry as usize, S::size(), None).unwrap();
+
+ // sanity check
+ debug_assert_eq!(storage.size(), S::size());
+
+ // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as
+ // usize));
+ let palette = new_palette_type.as_empty_palette();
+ PalettedContainer {
+ bits_per_entry,
+ palette,
+ storage,
+ }
+ }
+
+ fn on_resize(&mut self, bits_per_entry: u8, value: S) -> usize {
+ // in vanilla this is always true, but it's sometimes false in purpur servers
+ // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5");
+ let mut new_data = self.create_or_reuse_data(bits_per_entry);
+ new_data.copy_from(&self.palette, &self.storage);
+ *self = new_data;
+ self.id_for(value)
+ }
+
+ fn copy_from(&mut self, palette: &Palette<S>, storage: &BitStorage) {
+ for i in 0..storage.size() {
+ let value = palette.value_for(storage.get(i) as usize);
+ let id = self.id_for(value) as u64;
+ self.storage.set(i, id);
+ }
+ }
+
+ pub fn id_for(&mut self, value: S) -> usize {
+ match &mut self.palette {
+ Palette::SingleValue(v) => {
+ if (*v).into() != value.into() {
+ self.on_resize(1, value)
+ } else {
+ 0
+ }
+ }
+ Palette::Linear(palette) => {
+ if let Some(index) = palette.iter().position(|&v| v.into() == value.into()) {
+ return index;
+ }
+ let capacity = 2usize.pow(self.bits_per_entry.into());
+ if capacity > palette.len() {
+ palette.push(value);
+ palette.len() - 1
+ } else {
+ self.on_resize(self.bits_per_entry + 1, value)
+ }
+ }
+ Palette::Hashmap(palette) => {
+ // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked
+ // before changing it
+ if let Some(index) = palette.iter().position(|v| (*v).into() == value.into()) {
+ return index;
+ }
+ let capacity = 2usize.pow(self.bits_per_entry.into());
+ if capacity > palette.len() {
+ palette.push(value);
+ palette.len() - 1
+ } else {
+ self.on_resize(self.bits_per_entry + 1, value)
+ }
+ }
+ Palette::Global => value.into() as usize,
+ }
+ }
+}
+
+impl<S: PalletedContainerKind> AzaleaWrite for PalettedContainer<S> {
+ fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
+ self.bits_per_entry.azalea_write(buf)?;
+ self.palette.azalea_write(buf)?;
+ self.storage.data.azalea_write(buf)?;
+ Ok(())
+ }
+}
+
+impl<S: PalletedContainerKind> Default for PalettedContainer<S> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/azalea-world/src/palette/mod.rs b/azalea-world/src/palette/mod.rs
new file mode 100644
index 00000000..65a04f6a
--- /dev/null
+++ b/azalea-world/src/palette/mod.rs
@@ -0,0 +1,115 @@
+mod container;
+
+#[cfg(test)]
+mod tests;
+
+use std::{
+ fmt::Debug,
+ io::{self, Cursor, Write},
+};
+
+use azalea_buf::{AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
+pub use container::*;
+
+/// A representation of the different types of chunk palettes Minecraft uses.
+#[derive(Clone, Debug)]
+pub enum Palette<S: PalletedContainerKind> {
+ /// ID of the corresponding entry in its global palette
+ SingleValue(S),
+ // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure
+ // it's actually needed?
+ Linear(Vec<S>),
+ Hashmap(Vec<S>),
+ Global,
+}
+
+impl<S: PalletedContainerKind> Palette<S> {
+ pub fn value_for(&self, id: usize) -> S {
+ match self {
+ Palette::SingleValue(v) => *v,
+ Palette::Linear(v) => v.get(id).copied().unwrap_or_default(),
+ Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(),
+ Palette::Global => S::try_from(id as u32).unwrap_or_default(),
+ }
+ }
+}
+
+impl<S: PalletedContainerKind> AzaleaWrite for Palette<S> {
+ fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
+ match self {
+ Palette::SingleValue(value) => {
+ (*value).into().azalea_write_var(buf)?;
+ }
+ Palette::Linear(values) => {
+ (values.len() as u32).azalea_write_var(buf)?;
+ for value in values {
+ (*value).into().azalea_write_var(buf)?;
+ }
+ }
+ Palette::Hashmap(values) => {
+ (values.len() as u32).azalea_write_var(buf)?;
+ for value in values {
+ (*value).into().azalea_write_var(buf)?;
+ }
+ }
+ Palette::Global => {}
+ }
+ Ok(())
+ }
+}
+
+impl PaletteKind {
+ pub fn read<S: PalletedContainerKind>(
+ &self,
+ buf: &mut Cursor<&[u8]>,
+ ) -> Result<Palette<S>, BufReadError> {
+ Ok(match self {
+ // since they're read as varints it's actually fine to just use BlockStateIntegerRepr
+ // instead of the correct type (u32)
+ PaletteKind::SingleValue => {
+ Palette::SingleValue(S::try_from(u32::azalea_read_var(buf)?).unwrap_or_default())
+ }
+ PaletteKind::Linear => Palette::Linear(
+ Vec::<u32>::azalea_read_var(buf)?
+ .into_iter()
+ .map(|v| S::try_from(v).unwrap_or_default())
+ .collect(),
+ ),
+ PaletteKind::Hashmap => Palette::Hashmap(
+ Vec::<u32>::azalea_read_var(buf)?
+ .into_iter()
+ .map(|v| S::try_from(v).unwrap_or_default())
+ .collect(),
+ ),
+ PaletteKind::Global => Palette::Global,
+ })
+ }
+
+ pub fn as_empty_palette<S: PalletedContainerKind>(&self) -> Palette<S> {
+ match self {
+ PaletteKind::SingleValue => Palette::SingleValue(S::default()),
+ PaletteKind::Linear => Palette::Linear(Vec::new()),
+ PaletteKind::Hashmap => Palette::Hashmap(Vec::new()),
+ PaletteKind::Global => Palette::Global,
+ }
+ }
+}
+
+impl<S: PalletedContainerKind> From<&Palette<S>> for PaletteKind {
+ fn from(palette: &Palette<S>) -> Self {
+ match palette {
+ Palette::SingleValue(_) => PaletteKind::SingleValue,
+ Palette::Linear(_) => PaletteKind::Linear,
+ Palette::Hashmap(_) => PaletteKind::Hashmap,
+ Palette::Global => PaletteKind::Global,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PaletteKind {
+ SingleValue,
+ Linear,
+ Hashmap,
+ Global,
+}
diff --git a/azalea-world/src/palette/tests.rs b/azalea-world/src/palette/tests.rs
new file mode 100644
index 00000000..d1423306
--- /dev/null
+++ b/azalea-world/src/palette/tests.rs
@@ -0,0 +1,80 @@
+use azalea_block::BlockState;
+use azalea_core::position::ChunkSectionBlockPos;
+
+use super::*;
+
+#[test]
+fn test_resize_0_bits_to_1() {
+ let mut palette_container = PalettedContainer::<BlockState>::new();
+
+ assert_eq!(palette_container.bits_per_entry, 0);
+ assert_eq!(palette_container.get_at_index(0), BlockState::AIR);
+ assert_eq!(
+ PaletteKind::from(&palette_container.palette),
+ PaletteKind::SingleValue
+ );
+ let block_state_1 = BlockState::try_from(1_u32).unwrap();
+ palette_container.set_at_index(0, block_state_1);
+ assert_eq!(palette_container.get_at_index(0), block_state_1);
+ assert_eq!(
+ PaletteKind::from(&palette_container.palette),
+ PaletteKind::Linear
+ );
+}
+
+#[test]
+fn test_resize_0_bits_to_5() {
+ let mut palette_container = PalettedContainer::<BlockState>::new();
+
+ let set = |pc: &mut PalettedContainer<BlockState>, i, v: u32| {
+ pc.set_at_index(i, BlockState::try_from(v).unwrap());
+ };
+
+ set(&mut palette_container, 0, 0); // 0 bits
+ assert_eq!(palette_container.bits_per_entry, 0);
+
+ set(&mut palette_container, 1, 1); // 1 bit
+ assert_eq!(palette_container.bits_per_entry, 1);
+
+ set(&mut palette_container, 2, 2); // 2 bits
+ assert_eq!(palette_container.bits_per_entry, 2);
+ set(&mut palette_container, 3, 3);
+
+ set(&mut palette_container, 4, 4); // 3 bits
+ assert_eq!(palette_container.bits_per_entry, 3);
+ set(&mut palette_container, 5, 5);
+ set(&mut palette_container, 6, 6);
+ set(&mut palette_container, 7, 7);
+
+ set(&mut palette_container, 8, 8); // 4 bits
+ assert_eq!(palette_container.bits_per_entry, 4);
+ set(&mut palette_container, 9, 9);
+ set(&mut palette_container, 10, 10);
+ set(&mut palette_container, 11, 11);
+ set(&mut palette_container, 12, 12);
+ set(&mut palette_container, 13, 13);
+ set(&mut palette_container, 14, 14);
+ set(&mut palette_container, 15, 15);
+ assert_eq!(palette_container.bits_per_entry, 4);
+
+ set(&mut palette_container, 16, 16); // 5 bits
+ assert_eq!(palette_container.bits_per_entry, 5);
+}
+
+#[test]
+fn test_coords_from_index() {
+ let palette_container = PalettedContainer::<BlockState>::new();
+
+ for x in 0..15 {
+ for y in 0..15 {
+ for z in 0..15 {
+ assert_eq!(
+ palette_container.coords_from_index(
+ palette_container.index_from_coords(ChunkSectionBlockPos::new(x, y, z))
+ ),
+ ChunkSectionBlockPos::new(x, y, z)
+ );
+ }
+ }
+ }
+}
diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs
index 183f59b5..00bee13a 100644
--- a/azalea-world/src/world.rs
+++ b/azalea-world/src/world.rs
@@ -11,6 +11,7 @@ use azalea_core::{
position::{BlockPos, ChunkPos},
registry_holder::RegistryHolder,
};
+use azalea_registry::Biome;
use bevy_ecs::{component::Component, entity::Entity};
use derive_more::{Deref, DerefMut};
use nohash_hasher::IntMap;
@@ -170,6 +171,10 @@ impl Instance {
self.chunks.get_block_state(pos).map(FluidState::from)
}
+ pub fn get_biome(&self, pos: &BlockPos) -> Option<Biome> {
+ self.chunks.get_biome(pos)
+ }
+
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
self.chunks.set_block_state(pos, state)
}
diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs
index 013f7c2d..b77183e8 100644
--- a/azalea/src/pathfinder/world.rs
+++ b/azalea/src/pathfinder/world.rs
@@ -9,7 +9,7 @@ use azalea_core::{
position::{BlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos},
};
use azalea_physics::collision::BlockWithShape;
-use azalea_world::Instance;
+use azalea_world::{Instance, palette::PalettedContainer};
use parking_lot::RwLock;
use super::{mining::MiningCache, rel_block_pos::RelBlockPos};
@@ -26,7 +26,12 @@ pub struct CachedWorld {
// we store `PalettedContainer`s instead of `Chunk`s or `Section`s because it doesn't contain
// any unnecessary data like heightmaps or biomes.
- cached_chunks: RefCell<Vec<(ChunkPos, Vec<azalea_world::palette::PalettedContainer>)>>,
+ cached_chunks: RefCell<
+ Vec<(
+ ChunkPos,
+ Vec<azalea_world::palette::PalettedContainer<BlockState>>,
+ )>,
+ >,
last_chunk_cache_index: RefCell<Option<usize>>,
cached_blocks: UnsafeCell<CachedSections>,
@@ -119,7 +124,7 @@ impl CachedWorld {
fn with_section<T>(
&self,
section_pos: ChunkSectionPos,
- f: impl FnOnce(&azalea_world::palette::PalettedContainer) -> T,
+ f: impl FnOnce(&azalea_world::palette::PalettedContainer<BlockState>) -> T,
) -> Option<T> {
if section_pos.y * 16 < self.min_y {
// y position is out of bounds
@@ -143,7 +148,7 @@ impl CachedWorld {
// y position is out of bounds
return None;
};
- let section: &azalea_world::palette::PalettedContainer = &sections[section_index];
+ let section = &sections[section_index];
return Some(f(section));
}
@@ -165,7 +170,7 @@ impl CachedWorld {
return None;
};
*self.last_chunk_cache_index.borrow_mut() = Some(chunk_index);
- let section: &azalea_world::palette::PalettedContainer = &sections[section_index];
+ let section = &sections[section_index];
return Some(f(section));
}
@@ -173,11 +178,11 @@ impl CachedWorld {
let chunk = world.chunks.get(&chunk_pos)?;
let chunk = chunk.read();
- let sections: Vec<azalea_world::palette::PalettedContainer> = chunk
+ let sections = chunk
.sections
.iter()
.map(|section| section.states.clone())
- .collect();
+ .collect::<Vec<PalettedContainer<BlockState>>>();
if section_index >= sections.len() {
// y position is out of bounds