aboutsummaryrefslogtreecommitdiff
path: root/azalea-world/src
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-08-29 20:41:01 -0500
committerGitHub <noreply@github.com>2022-08-29 20:41:01 -0500
commitf42d630544165d11a544224ac273d6aaf89d8095 (patch)
tree94bd73771ecb582d89a87cdca8e21b2d6573ef12 /azalea-world/src
parent2ea804401f54a45765860201d10d0569d07862ec (diff)
downloadazalea-drasl-f42d630544165d11a544224ac273d6aaf89d8095.tar.xz
Physics (#11)
* Put physics module in azalea-entity * port aabb * add more stuff to PositionXYZ * azalea-physics * important collision things * more physics stuff * backup because i'm about to delete shapes * more shape stuff * CubeVoxelShape * no compile errors??? insane * impl VoxelShape for ArrayVoxelShape * Shapes stuff * collide_x but it doesn't work yet * binary_search * it compiles * Entity has bounding box * Update discrete_voxel_shape.rs * Entity::make_bounding_box * ok i'm about to merge az-entity and az-world might be a terrible idea which is why i'm committing first * ok so i moved entity to world * on_pos and move_entity compiles * add send_position * move collision stuff to collision module in az-physics * dimension is no longer an Option * start trying to do collision for the client * collision works :tada: * start adding palette resizing * get_and_set (pain) * it compiles but probably won't work * add a test * remove printlns * add more tests for palette stuff * ClientboundMoveVec3Packet -> ClientboundMoveEntityPosPacket i think i changed this on accident once * palette resizing works todo: remove the printlns * Remove printlns in palette.rs * fix issues from merge * fixes + work a bit more on physics * Better entities (#19) * well it compiles * add tests to entity storage * add suggestions in azalea-brigadier * this probably causes ub * fix brigadiersuggestions * get rid of entityid * test From<EntityMut> for EntityRef * don't mention other libraries since there's too many * fix warnings * do todos in brigadier suggestions * work on physics * more physics stuff * remove trait feature on az-block i think rust gets confused and compiles the macro without the feature * bump ahash * aes tests in az-crypto * optimize aes's deps * fix crashes * fix section_index for negative numbers and test * fix BlockPos protocol implementation * remove some debug prints * prepare to add ai_step * make ai step work * clippy
Diffstat (limited to 'azalea-world/src')
-rw-r--r--azalea-world/src/bit_storage.rs51
-rw-r--r--azalea-world/src/chunk_storage.rs (renamed from azalea-world/src/chunk.rs)129
-rw-r--r--azalea-world/src/entity/data.rs148
-rw-r--r--azalea-world/src/entity/dimensions.rs23
-rw-r--r--azalea-world/src/entity/mod.rs316
-rw-r--r--azalea-world/src/entity_storage.rs (renamed from azalea-world/src/entity.rs)101
-rw-r--r--azalea-world/src/lib.rs86
-rw-r--r--azalea-world/src/palette.rs318
8 files changed, 1012 insertions, 160 deletions
diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs
index fcb3f8f9..2626c312 100644
--- a/azalea-world/src/bit_storage.rs
+++ b/azalea-world/src/bit_storage.rs
@@ -103,16 +103,21 @@ impl BitStorage {
/// Create a new BitStorage with the given number of bits per entry.
/// `size` is the number of entries in the BitStorage.
pub fn new(bits: usize, size: usize, data: Option<Vec<u64>>) -> Result<Self, BitStorageError> {
- // vanilla has this assert but it's not always true for some reason??
- // assert!(bits >= 1 && bits <= 32);
-
if let Some(data) = &data {
+ // 0 bit storage
if data.is_empty() {
- // TODO: make 0 bit storage actually work
- return Ok(BitStorage::default());
+ return Ok(BitStorage {
+ data: Vec::with_capacity(0),
+ bits,
+ size,
+ ..Default::default()
+ });
}
}
+ // vanilla has this assert but it's not always true for some reason??
+ // assert!(bits >= 1 && bits <= 32);
+
let values_per_long = 64 / bits;
let magic_index = values_per_long - 1;
let (divide_mul, divide_add, divide_shift) = MAGIC[magic_index as usize];
@@ -163,23 +168,43 @@ impl BitStorage {
assert!(
index < self.size,
- "Index {} out of bounds (max is {})",
+ "Index {} out of bounds (must be less than {})",
index,
- self.size - 1
+ self.size
);
+
+ // 0 bit storage
+ if self.data.is_empty() {
+ return 0;
+ }
+
let cell_index = self.cell_index(index as u64);
let cell = &self.data[cell_index as usize];
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
cell >> bit_index & self.mask
}
+ pub fn get_and_set(&mut self, index: usize, value: u64) -> u64 {
+ // 0 bit storage
+ if self.data.is_empty() {
+ return 0;
+ }
+
+ assert!(index < self.size);
+ assert!(value <= self.mask);
+ let cell_index = self.cell_index(index as u64);
+ let cell = &mut self.data[cell_index as usize];
+ let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
+ let old_value = *cell >> (bit_index as u64) & self.mask;
+ *cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index;
+ old_value
+ }
+
pub fn set(&mut self, index: usize, value: u64) {
- // Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1);
- // Validate.inclusiveBetween(0L, this.mask, (long)var2);
- // int var3 = this.cellIndex(var1);
- // long var4 = this.data[var3];
- // int var6 = (var1 - var3 * this.valuesPerLong) * this.bits;
- // this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6;
+ // 0 bit storage
+ if self.data.is_empty() {
+ return;
+ }
assert!(index < self.size);
assert!(value <= self.mask);
diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk_storage.rs
index 9d393c2d..4ceea347 100644
--- a/azalea-world/src/chunk.rs
+++ b/azalea-world/src/chunk_storage.rs
@@ -4,6 +4,7 @@ use crate::Dimension;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable};
+use azalea_core::floor_mod;
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use std::fmt::Debug;
use std::{
@@ -24,13 +25,33 @@ pub struct ChunkStorage {
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
}
-// java moment
-// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case
-fn floor_mod(x: i32, y: u32) -> u32 {
- if x < 0 {
- y - ((-x) as u32 % y)
- } else {
- x as u32 % y
+#[derive(Debug)]
+pub struct Chunk {
+ pub sections: Vec<Section>,
+}
+
+#[derive(Clone, Debug)]
+pub struct Section {
+ pub block_count: u16,
+ pub states: PalettedContainer,
+ pub biomes: PalettedContainer,
+}
+
+impl Default for Section {
+ fn default() -> Self {
+ Section {
+ block_count: 0,
+ states: PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(),
+ biomes: PalettedContainer::new(&PalettedContainerType::Biomes).unwrap(),
+ }
+ }
+}
+
+impl Default for Chunk {
+ fn default() -> Self {
+ Chunk {
+ sections: vec![Section::default(); (384 / 16) as usize],
+ }
}
}
@@ -59,13 +80,24 @@ impl ChunkStorage {
pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
- println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
let chunk = &self[&chunk_pos];
chunk
.as_ref()
.map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y))
}
+ pub fn set_block_state(&self, pos: &BlockPos, state: BlockState, min_y: i32) -> BlockState {
+ let chunk_pos = ChunkPos::from(pos);
+ let chunk = &self[&chunk_pos];
+ if let Some(chunk) = chunk.as_ref() {
+ let mut chunk = chunk.lock().unwrap();
+ chunk.get_and_set(&ChunkBlockPos::from(pos), state, min_y)
+ } else {
+ // nothing is in this chunk, just return air
+ BlockState::Air
+ }
+ }
+
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
@@ -104,11 +136,6 @@ impl IndexMut<&ChunkPos> for ChunkStorage {
}
}
-#[derive(Debug)]
-pub struct Chunk {
- pub sections: Vec<Section>,
-}
-
impl Chunk {
pub fn read_with_dimension(
buf: &mut impl Read,
@@ -131,9 +158,7 @@ impl Chunk {
}
pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
- // TODO: check the build height and stuff, this code will be broken if the min build height is 0
- // (LevelHeightAccessor.getMinSection in vanilla code)
- assert!(y >= 0);
+ assert!(y >= min_y, "y ({}) must be at least {}", y, min_y);
let min_section_index = min_y.div_floor(16);
(y.div_floor(16) - min_section_index) as u32
}
@@ -145,6 +170,27 @@ impl Chunk {
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.get(chunk_section_pos)
}
+
+ pub fn get_and_set(
+ &mut self,
+ pos: &ChunkBlockPos,
+ state: BlockState,
+ min_y: i32,
+ ) -> BlockState {
+ let section_index = self.section_index(pos.y, min_y);
+ // TODO: make sure the section exists
+ let section = &mut self.sections[section_index as usize];
+ let chunk_section_pos = ChunkSectionBlockPos::from(pos);
+ section.get_and_set(chunk_section_pos, state)
+ }
+
+ pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
+ let section_index = self.section_index(pos.y, min_y);
+ // TODO: make sure the section exists
+ let section = &mut self.sections[section_index as usize];
+ let chunk_section_pos = ChunkSectionBlockPos::from(pos);
+ section.set(chunk_section_pos, state)
+ }
}
impl McBufWritable for Chunk {
@@ -170,13 +216,6 @@ impl Debug for ChunkStorage {
}
}
-#[derive(Clone, Debug)]
-pub struct Section {
- pub block_count: u16,
- pub states: PalettedContainer,
- pub biomes: PalettedContainer,
-}
-
impl McBufReadable for Section {
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
let block_count = u16::read_from(buf)?;
@@ -220,9 +259,47 @@ impl McBufWritable for Section {
impl Section {
fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
// TODO: use the unsafe method and do the check earlier
+ let state = self
+ .states
+ .get(pos.x as usize, pos.y as usize, pos.z as usize);
+ // if there's an unknown block assume it's air
+ BlockState::try_from(state).unwrap_or(BlockState::Air)
+ }
+
+ fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState {
+ let previous_state =
+ self.states
+ .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
+ // if there's an unknown block assume it's air
+ BlockState::try_from(previous_state).unwrap_or(BlockState::Air)
+ }
+
+ fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
self.states
- .get(pos.x as usize, pos.y as usize, pos.z as usize)
- .try_into()
- .expect("Invalid block state.")
+ .set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32);
+ }
+}
+
+impl Default for ChunkStorage {
+ fn default() -> Self {
+ Self::new(8, 384, -64)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_section_index() {
+ let chunk = Chunk::default();
+ assert_eq!(chunk.section_index(0, 0), 0);
+ assert_eq!(chunk.section_index(128, 0), 8);
+ assert_eq!(chunk.section_index(127, 0), 7);
+ assert_eq!(chunk.section_index(0, -64), 4);
+ assert_eq!(chunk.section_index(-64, -64), 0);
+ assert_eq!(chunk.section_index(-49, -64), 0);
+ assert_eq!(chunk.section_index(-48, -64), 1);
+ assert_eq!(chunk.section_index(128, -64), 12);
}
}
diff --git a/azalea-world/src/entity/data.rs b/azalea-world/src/entity/data.rs
new file mode 100644
index 00000000..ff708653
--- /dev/null
+++ b/azalea-world/src/entity/data.rs
@@ -0,0 +1,148 @@
+use azalea_buf::{BufReadError, McBufVarReadable};
+use azalea_buf::{McBuf, McBufReadable, McBufWritable};
+use azalea_chat::component::Component;
+use azalea_core::{BlockPos, Direction, Particle, Slot};
+use std::io::{Read, Write};
+use uuid::Uuid;
+
+#[derive(Clone, Debug)]
+pub struct EntityMetadata(Vec<EntityDataItem>);
+
+#[derive(Clone, Debug)]
+pub struct EntityDataItem {
+ // we can't identify what the index is for here because we don't know the
+ // entity type
+ pub index: u8,
+ pub value: EntityDataValue,
+}
+
+impl McBufReadable for EntityMetadata {
+ fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
+ let mut metadata = Vec::new();
+ loop {
+ let index = u8::read_from(buf)?;
+ if index == 0xff {
+ break;
+ }
+ let value = EntityDataValue::read_from(buf)?;
+ metadata.push(EntityDataItem { index, value });
+ }
+ Ok(EntityMetadata(metadata))
+ }
+}
+
+impl McBufWritable for EntityMetadata {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ for item in &self.0 {
+ item.index.write_into(buf)?;
+ item.value.write_into(buf)?;
+ }
+ 0xffu8.write_into(buf)?;
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum EntityDataValue {
+ Byte(u8),
+ // varint
+ Int(i32),
+ Float(f32),
+ String(String),
+ Component(Component),
+ OptionalComponent(Option<Component>),
+ ItemStack(Slot),
+ Boolean(bool),
+ Rotations { x: f32, y: f32, z: f32 },
+ BlockPos(BlockPos),
+ OptionalBlockPos(Option<BlockPos>),
+ Direction(Direction),
+ OptionalUuid(Option<Uuid>),
+ // 0 for absent (implies air); otherwise, a block state ID as per the global palette
+ // this is a varint
+ OptionalBlockState(Option<i32>),
+ CompoundTag(azalea_nbt::Tag),
+ Particle(Particle),
+ VillagerData(VillagerData),
+ // 0 for absent; 1 + actual value otherwise. Used for entity IDs.
+ OptionalUnsignedInt(Option<u32>),
+ Pose(Pose),
+}
+
+impl McBufReadable for EntityDataValue {
+ fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
+ let data_type = u32::var_read_from(buf)?;
+ Ok(match data_type {
+ 0 => EntityDataValue::Byte(u8::read_from(buf)?),
+ 1 => EntityDataValue::Int(i32::var_read_from(buf)?),
+ 2 => EntityDataValue::Float(f32::read_from(buf)?),
+ 3 => EntityDataValue::String(String::read_from(buf)?),
+ 4 => EntityDataValue::Component(Component::read_from(buf)?),
+ 5 => EntityDataValue::OptionalComponent(Option::<Component>::read_from(buf)?),
+ 6 => EntityDataValue::ItemStack(Slot::read_from(buf)?),
+ 7 => EntityDataValue::Boolean(bool::read_from(buf)?),
+ 8 => EntityDataValue::Rotations {
+ x: f32::read_from(buf)?,
+ y: f32::read_from(buf)?,
+ z: f32::read_from(buf)?,
+ },
+ 9 => EntityDataValue::BlockPos(BlockPos::read_from(buf)?),
+ 10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_from(buf)?),
+ 11 => EntityDataValue::Direction(Direction::read_from(buf)?),
+ 12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_from(buf)?),
+ 13 => EntityDataValue::OptionalBlockState({
+ let val = i32::read_from(buf)?;
+ if val == 0 {
+ None
+ } else {
+ Some(val)
+ }
+ }),
+ 14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_from(buf)?),
+ 15 => EntityDataValue::Particle(Particle::read_from(buf)?),
+ 16 => EntityDataValue::VillagerData(VillagerData::read_from(buf)?),
+ 17 => EntityDataValue::OptionalUnsignedInt({
+ let val = u32::var_read_from(buf)?;
+ if val == 0 {
+ None
+ } else {
+ Some((val - 1) as u32)
+ }
+ }),
+ 18 => EntityDataValue::Pose(Pose::read_from(buf)?),
+ _ => {
+ return Err(BufReadError::UnexpectedEnumVariant {
+ id: data_type as i32,
+ })
+ }
+ })
+ }
+}
+
+impl McBufWritable for EntityDataValue {
+ fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
+ todo!();
+ }
+}
+
+#[derive(Clone, Debug, Copy, McBuf)]
+pub enum Pose {
+ Standing = 0,
+ FallFlying = 1,
+ Sleeping = 2,
+ Swimming = 3,
+ SpinAttack = 4,
+ Sneaking = 5,
+ LongJumping = 6,
+ Dying = 7,
+}
+
+#[derive(Debug, Clone, McBuf)]
+pub struct VillagerData {
+ #[var]
+ type_: u32,
+ #[var]
+ profession: u32,
+ #[var]
+ level: u32,
+}
diff --git a/azalea-world/src/entity/dimensions.rs b/azalea-world/src/entity/dimensions.rs
new file mode 100644
index 00000000..1d013d10
--- /dev/null
+++ b/azalea-world/src/entity/dimensions.rs
@@ -0,0 +1,23 @@
+use azalea_core::{Vec3, AABB};
+
+#[derive(Debug, Default)]
+pub struct EntityDimensions {
+ pub width: f32,
+ pub height: f32,
+}
+
+impl EntityDimensions {
+ pub fn make_bounding_box(&self, pos: &Vec3) -> AABB {
+ let radius = (self.width / 2.0) as f64;
+ let height = self.height as f64;
+ AABB {
+ min_x: pos.x - radius,
+ min_y: pos.y,
+ min_z: pos.z - radius,
+
+ max_x: pos.x + radius,
+ max_y: pos.y + height,
+ max_z: pos.z + radius,
+ }
+ }
+}
diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs
new file mode 100644
index 00000000..37321e0a
--- /dev/null
+++ b/azalea-world/src/entity/mod.rs
@@ -0,0 +1,316 @@
+mod data;
+mod dimensions;
+
+use crate::Dimension;
+use azalea_core::{BlockPos, Vec3, AABB};
+pub use data::*;
+pub use dimensions::*;
+use std::ops::{Deref, DerefMut};
+use std::ptr::NonNull;
+use uuid::Uuid;
+
+#[derive(Debug)]
+pub struct EntityRef<'d> {
+ /// The dimension this entity is in.
+ pub dimension: &'d Dimension,
+ /// The incrementing numerical id of the entity.
+ pub id: u32,
+ pub data: &'d EntityData,
+}
+
+impl<'d> EntityRef<'d> {
+ pub fn new(dimension: &'d Dimension, id: u32, data: &'d EntityData) -> Self {
+ // TODO: have this be based on the entity type
+ Self {
+ dimension,
+ id,
+ data,
+ }
+ }
+}
+
+impl<'d> EntityRef<'d> {
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub fn make_bounding_box(&self) -> AABB {
+ self.dimensions.make_bounding_box(&self.pos())
+ }
+
+ /// Get the position of the block below the entity, but a little lower.
+ pub fn on_pos_legacy(&self) -> BlockPos {
+ self.on_pos(0.2)
+ }
+
+ // int x = Mth.floor(this.position.x);
+ // int y = Mth.floor(this.position.y - (double)var1);
+ // int z = Mth.floor(this.position.z);
+ // BlockPos var5 = new BlockPos(x, y, z);
+ // if (this.level.getBlockState(var5).isAir()) {
+ // BlockPos var6 = var5.below();
+ // BlockState var7 = this.level.getBlockState(var6);
+ // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) {
+ // return var6;
+ // }
+ // }
+ // return var5;
+ pub fn on_pos(&self, offset: f32) -> BlockPos {
+ let x = self.pos().x.floor() as i32;
+ let y = (self.pos().y - offset as f64).floor() as i32;
+ let z = self.pos().z.floor() as i32;
+ let pos = BlockPos { x, y, z };
+
+ // TODO: check if block below is a fence, wall, or fence gate
+ // let block_pos = pos.below();
+ // let block_state = dimension.get_block_state(&block_pos);
+ // if block_state == Some(BlockState::Air) {
+ // let block_pos_below = block_pos.below();
+ // let block_state_below = dimension.get_block_state(&block_pos_below);
+ // if let Some(block_state_below) = block_state_below {
+ // if block_state_below.is_fence()
+ // || block_state_below.is_wall()
+ // || block_state_below.is_fence_gate()
+ // {
+ // return block_pos_below;
+ // }
+ // }
+ // }
+
+ pos
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityMut<'d> {
+ /// The dimension this entity is in.
+ pub dimension: &'d mut Dimension,
+ /// The incrementing numerical id of the entity.
+ pub id: u32,
+ pub data: NonNull<EntityData>,
+}
+
+impl<'d> EntityMut<'d> {
+ pub fn new(dimension: &'d mut Dimension, id: u32, data: NonNull<EntityData>) -> Self {
+ Self {
+ dimension,
+ id,
+ data,
+ }
+ }
+
+ /// Sets the position of the entity. This doesn't update the cache in
+ /// azalea-world, and should only be used within azalea-world!
+ pub unsafe fn move_unchecked(&mut self, new_pos: Vec3) {
+ self.pos = new_pos;
+ let bounding_box = self.make_bounding_box();
+ self.bounding_box = bounding_box;
+ }
+
+ pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
+ self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0;
+ self.x_rot = x_rot % 360.0;
+ // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so
+ }
+
+ pub fn move_relative(&mut self, speed: f32, acceleration: &Vec3) {
+ let input_vector = self.input_vector(speed, acceleration);
+ self.delta += input_vector;
+ }
+
+ pub fn input_vector(&self, speed: f32, acceleration: &Vec3) -> Vec3 {
+ let distance = acceleration.length_squared();
+ if distance < 1.0E-7 {
+ return Vec3::default();
+ }
+ let acceleration = if distance > 1.0 {
+ acceleration.normalize()
+ } else {
+ *acceleration
+ }
+ .scale(speed as f64);
+ let y_rot = f32::sin(self.y_rot * 0.017453292f32);
+ let x_rot = f32::cos(self.y_rot * 0.017453292f32);
+ Vec3 {
+ x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64),
+ y: acceleration.y,
+ z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64),
+ }
+ }
+}
+
+impl<'d> EntityMut<'d> {
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub fn make_bounding_box(&self) -> AABB {
+ self.dimensions.make_bounding_box(&self.pos())
+ }
+
+ /// Get the position of the block below the entity, but a little lower.
+ pub fn on_pos_legacy(&self) -> BlockPos {
+ self.on_pos(0.2)
+ }
+
+ // int x = Mth.floor(this.position.x);
+ // int y = Mth.floor(this.position.y - (double)var1);
+ // int z = Mth.floor(this.position.z);
+ // BlockPos var5 = new BlockPos(x, y, z);
+ // if (this.level.getBlockState(var5).isAir()) {
+ // BlockPos var6 = var5.below();
+ // BlockState var7 = this.level.getBlockState(var6);
+ // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) {
+ // return var6;
+ // }
+ // }
+ // return var5;
+ pub fn on_pos(&self, offset: f32) -> BlockPos {
+ let x = self.pos().x.floor() as i32;
+ let y = (self.pos().y - offset as f64).floor() as i32;
+ let z = self.pos().z.floor() as i32;
+ let pos = BlockPos { x, y, z };
+
+ // TODO: check if block below is a fence, wall, or fence gate
+ // let block_pos = pos.below();
+ // let block_state = dimension.get_block_state(&block_pos);
+ // if block_state == Some(BlockState::Air) {
+ // let block_pos_below = block_pos.below();
+ // let block_state_below = dimension.get_block_state(&block_pos_below);
+ // if let Some(block_state_below) = block_state_below {
+ // if block_state_below.is_fence()
+ // || block_state_below.is_wall()
+ // || block_state_below.is_fence_gate()
+ // {
+ // return block_pos_below;
+ // }
+ // }
+ // }
+
+ pos
+ }
+}
+
+impl<'d> From<EntityMut<'d>> for EntityRef<'d> {
+ fn from(entity: EntityMut<'d>) -> EntityRef<'d> {
+ let data = unsafe { entity.data.as_ref() };
+ EntityRef {
+ dimension: entity.dimension,
+ id: entity.id,
+ data,
+ }
+ }
+}
+
+impl Deref for EntityMut<'_> {
+ type Target = EntityData;
+
+ fn deref(&self) -> &Self::Target {
+ unsafe { self.data.as_ref() }
+ }
+}
+
+impl DerefMut for EntityMut<'_> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { self.data.as_mut() }
+ }
+}
+
+impl Deref for EntityRef<'_> {
+ type Target = EntityData;
+
+ fn deref(&self) -> &Self::Target {
+ self.data
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityData {
+ pub uuid: Uuid,
+ /// The position of the entity right now.
+ /// This can be changde with unsafe_move, but the correct way is with dimension.move_entity
+ pos: Vec3,
+ /// The position of the entity last tick.
+ pub last_pos: Vec3,
+ pub delta: Vec3,
+
+ /// X acceleration.
+ pub xxa: f32,
+ /// Y acceleration.
+ pub yya: f32,
+ /// Z acceleration.
+ pub zza: f32,
+
+ pub x_rot: f32,
+ pub y_rot: f32,
+
+ pub x_rot_last: f32,
+ pub y_rot_last: f32,
+
+ pub on_ground: bool,
+ pub last_on_ground: bool,
+
+ /// The width and height of the entity.
+ pub dimensions: EntityDimensions,
+ /// The bounding box of the entity. This is more than just width and height, unlike dimensions.
+ pub bounding_box: AABB,
+}
+
+impl EntityData {
+ pub fn new(uuid: Uuid, pos: Vec3) -> Self {
+ let dimensions = EntityDimensions {
+ width: 0.8,
+ height: 1.8,
+ };
+
+ Self {
+ uuid,
+ pos,
+ last_pos: pos,
+ delta: Vec3::default(),
+
+ xxa: 0.,
+ yya: 0.,
+ zza: 0.,
+
+ x_rot: 0.,
+ y_rot: 0.,
+
+ y_rot_last: 0.,
+ x_rot_last: 0.,
+
+ on_ground: false,
+ last_on_ground: false,
+
+ // TODO: have this be based on the entity type
+ bounding_box: dimensions.make_bounding_box(&pos),
+ dimensions,
+ }
+ }
+
+ #[inline]
+ pub fn pos(&self) -> &Vec3 {
+ &self.pos
+ }
+
+ pub(crate) unsafe fn as_ptr(&mut self) -> NonNull<EntityData> {
+ NonNull::new_unchecked(self as *mut EntityData)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn from_mut_entity_to_ref_entity() {
+ let mut dim = Dimension::default();
+ let uuid = Uuid::from_u128(100);
+ dim.add_entity(0, EntityData::new(uuid, Vec3::default()));
+ let entity: EntityMut = dim.entity_mut(0).unwrap();
+ let entity_ref: EntityRef = entity.into();
+ assert_eq!(entity_ref.uuid, uuid);
+ }
+}
diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity_storage.rs
index 69529294..c7fd9c0b 100644
--- a/azalea-world/src/entity.rs
+++ b/azalea-world/src/entity_storage.rs
@@ -1,5 +1,5 @@
+use crate::entity::EntityData;
use azalea_core::ChunkPos;
-use azalea_entity::Entity;
use log::warn;
use nohash_hasher::{IntMap, IntSet};
use std::collections::HashMap;
@@ -7,41 +7,41 @@ use uuid::Uuid;
#[derive(Debug)]
pub struct EntityStorage {
- by_id: IntMap<u32, Entity>,
- by_chunk: HashMap<ChunkPos, IntSet<u32>>,
- by_uuid: HashMap<Uuid, u32>,
+ data_by_id: IntMap<u32, EntityData>,
+ id_by_chunk: HashMap<ChunkPos, IntSet<u32>>,
+ id_by_uuid: HashMap<Uuid, u32>,
}
impl EntityStorage {
pub fn new() -> Self {
Self {
- by_id: IntMap::default(),
- by_chunk: HashMap::default(),
- by_uuid: HashMap::default(),
+ data_by_id: IntMap::default(),
+ id_by_chunk: HashMap::default(),
+ id_by_uuid: HashMap::default(),
}
}
/// Add an entity to the storage.
#[inline]
- pub fn insert(&mut self, entity: Entity) {
- self.by_chunk
+ pub fn insert(&mut self, id: u32, entity: EntityData) {
+ self.id_by_chunk
.entry(ChunkPos::from(entity.pos()))
.or_default()
- .insert(entity.id);
- self.by_uuid.insert(entity.uuid, entity.id);
- self.by_id.insert(entity.id, entity);
+ .insert(id);
+ self.id_by_uuid.insert(entity.uuid, id);
+ self.data_by_id.insert(id, entity);
}
/// Remove an entity from the storage by its id.
#[inline]
pub fn remove_by_id(&mut self, id: u32) {
- if let Some(entity) = self.by_id.remove(&id) {
+ if let Some(entity) = self.data_by_id.remove(&id) {
let entity_chunk = ChunkPos::from(entity.pos());
let entity_uuid = entity.uuid;
- if self.by_chunk.remove(&entity_chunk).is_none() {
+ if self.id_by_chunk.remove(&entity_chunk).is_none() {
warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found.");
}
- if self.by_uuid.remove(&entity_uuid).is_none() {
+ if self.id_by_uuid.remove(&entity_uuid).is_none() {
warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found.");
}
} else {
@@ -49,36 +49,46 @@ impl EntityStorage {
}
}
+ /// Check if there is an entity that exists with the given id.
+ #[inline]
+ pub fn contains_id(&self, id: &u32) -> bool {
+ self.data_by_id.contains_key(id)
+ }
+
/// Get a reference to an entity by its id.
#[inline]
- pub fn get_by_id(&self, id: u32) -> Option<&Entity> {
- self.by_id.get(&id)
+ pub fn get_by_id(&self, id: u32) -> Option<&EntityData> {
+ self.data_by_id.get(&id)
}
/// Get a mutable reference to an entity by its id.
#[inline]
- pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> {
- self.by_id.get_mut(&id)
+ pub fn get_mut_by_id<'d>(&'d mut self, id: u32) -> Option<&'d mut EntityData> {
+ self.data_by_id.get_mut(&id)
}
/// Get a reference to an entity by its uuid.
#[inline]
- pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
- self.by_uuid.get(uuid).and_then(|id| self.by_id.get(id))
+ pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
+ self.id_by_uuid
+ .get(uuid)
+ .and_then(|id| self.data_by_id.get(id))
}
/// Get a mutable reference to an entity by its uuid.
#[inline]
- pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Entity> {
- self.by_uuid.get(uuid).and_then(|id| self.by_id.get_mut(id))
+ pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> {
+ self.id_by_uuid
+ .get(uuid)
+ .and_then(|id| self.data_by_id.get_mut(id))
}
/// Clear all entities in a chunk.
pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
- if let Some(entities) = self.by_chunk.remove(chunk) {
+ if let Some(entities) = self.id_by_chunk.remove(chunk) {
for entity_id in entities {
- if let Some(entity) = self.by_id.remove(&entity_id) {
- self.by_uuid.remove(&entity.uuid);
+ if let Some(entity) = self.data_by_id.remove(&entity_id) {
+ self.id_by_uuid.remove(&entity.uuid);
} else {
warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}.");
}
@@ -94,10 +104,10 @@ impl EntityStorage {
old_chunk: &ChunkPos,
new_chunk: &ChunkPos,
) {
- if let Some(entities) = self.by_chunk.get_mut(old_chunk) {
+ if let Some(entities) = self.id_by_chunk.get_mut(old_chunk) {
entities.remove(&entity_id);
}
- self.by_chunk
+ self.id_by_chunk
.entry(*new_chunk)
.or_default()
.insert(entity_id);
@@ -105,24 +115,24 @@ impl EntityStorage {
/// Get an iterator over all entities.
#[inline]
- pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
- self.by_id.values()
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
+ self.data_by_id.values()
}
- pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
self.entities().find(|&entity| f(entity))
}
- pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
- if let Some(entities) = self.by_chunk.get(chunk) {
+ if let Some(entities) = self.id_by_chunk.get(chunk) {
for entity_id in entities {
- if let Some(entity) = self.by_id.get(entity_id) {
+ if let Some(entity) = self.data_by_id.get(entity_id) {
if f(entity) {
return Some(entity);
}
@@ -138,3 +148,22 @@ impl Default for EntityStorage {
Self::new()
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use azalea_core::Vec3;
+
+ #[test]
+ fn test_store_entity() {
+ let mut storage = EntityStorage::new();
+ assert!(storage.get_by_id(0).is_none());
+
+ let uuid = Uuid::from_u128(100);
+ storage.insert(0, EntityData::new(uuid, Vec3::default()));
+ assert_eq!(storage.get_by_id(0).unwrap().uuid, uuid);
+
+ storage.remove_by_id(0);
+ assert!(storage.get_by_id(0).is_none());
+ }
+}
diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs
index 646ba1d9..77bb0f0f 100644
--- a/azalea-world/src/lib.rs
+++ b/azalea-world/src/lib.rs
@@ -1,17 +1,18 @@
#![feature(int_roundings)]
mod bit_storage;
-mod chunk;
-mod entity;
+mod chunk_storage;
+pub mod entity;
+mod entity_storage;
mod palette;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
-use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8};
-use azalea_entity::Entity;
+use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
pub use bit_storage::BitStorage;
-pub use chunk::{Chunk, ChunkStorage};
-pub use entity::EntityStorage;
+pub use chunk_storage::{Chunk, ChunkStorage};
+use entity::{EntityData, EntityMut, EntityRef};
+pub use entity_storage::EntityStorage;
use std::{
io::Read,
ops::{Index, IndexMut},
@@ -20,17 +21,10 @@ use std::{
use thiserror::Error;
use uuid::Uuid;
-#[cfg(test)]
-mod tests {
- #[test]
- fn it_works() {
- let result = 2 + 2;
- assert_eq!(result, 4);
- }
-}
-
/// A dimension is a collection of chunks and entities.
-#[derive(Debug)]
+/// Minecraft calls these "Levels", Fabric calls them "Worlds", Minestom calls them "Instances".
+/// Yeah.
+#[derive(Debug, Default)]
pub struct Dimension {
chunk_storage: ChunkStorage,
entity_storage: EntityStorage,
@@ -66,20 +60,20 @@ impl Dimension {
self.chunk_storage.get_block_state(pos, self.min_y())
}
- pub fn move_entity(
- &mut self,
- entity_id: u32,
- new_pos: EntityPos,
- ) -> Result<(), MoveEntityError> {
- let entity = self
- .entity_storage
- .get_mut_by_id(entity_id)
+ pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> BlockState {
+ self.chunk_storage.set_block_state(pos, state, self.min_y())
+ }
+
+ pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
+ println!("set_entity_pos({}, {:?})", entity_id, new_pos);
+ let mut entity = self
+ .entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
- entity.unsafe_move(new_pos);
+ unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
@@ -92,9 +86,8 @@ impl Dimension {
entity_id: u32,
delta: &PositionDelta8,
) -> Result<(), MoveEntityError> {
- let entity = self
- .entity_storage
- .get_mut_by_id(entity_id)
+ let mut entity = self
+ .entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let new_pos = entity.pos().with_delta(delta);
@@ -102,7 +95,7 @@ impl Dimension {
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
- entity.unsafe_move(new_pos);
+ unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
@@ -110,8 +103,8 @@ impl Dimension {
Ok(())
}
- pub fn add_entity(&mut self, entity: Entity) {
- self.entity_storage.insert(entity);
+ pub fn add_entity(&mut self, id: u32, entity: EntityData) {
+ self.entity_storage.insert(id, entity);
}
pub fn height(&self) -> u32 {
@@ -122,27 +115,46 @@ impl Dimension {
self.chunk_storage.min_y
}
- pub fn entity_by_id(&self, id: u32) -> Option<&Entity> {
+ pub fn entity_data_by_id(&self, id: u32) -> Option<&EntityData> {
self.entity_storage.get_by_id(id)
}
- pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> {
+ pub fn entity_data_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> {
self.entity_storage.get_mut_by_id(id)
}
- pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
+ pub fn entity<'d>(&'d self, id: u32) -> Option<EntityRef<'d>> {
+ let entity_data = self.entity_storage.get_by_id(id);
+ if let Some(entity_data) = entity_data {
+ Some(EntityRef::new(self, id, entity_data))
+ } else {
+ None
+ }
+ }
+
+ pub fn entity_mut<'d>(&'d mut self, id: u32) -> Option<EntityMut<'d>> {
+ let entity_data = self.entity_storage.get_mut_by_id(id);
+ if let Some(entity_data) = entity_data {
+ let entity_ptr = unsafe { entity_data.as_ptr() };
+ Some(EntityMut::new(self, id, entity_ptr))
+ } else {
+ None
+ }
+ }
+
+ pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
self.entity_storage.get_by_uuid(uuid)
}
/// Get an iterator over all entities.
#[inline]
- pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
self.entity_storage.entities()
}
- pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
where
- F: FnMut(&Entity) -> bool,
+ F: FnMut(&EntityData) -> bool,
{
self.entity_storage.find_one_entity(|entity| f(entity))
}
diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs
index 2473a7e5..4e0f9a96 100644
--- a/azalea-world/src/palette.rs
+++ b/azalea-world/src/palette.rs
@@ -21,23 +21,27 @@ pub struct PalettedContainer {
}
impl PalettedContainer {
+ pub fn new(container_type: &'static PalettedContainerType) -> Result<Self, String> {
+ let palette = Palette::SingleValue(0);
+ let size = container_type.size();
+ let storage = BitStorage::new(0, size, Some(vec![])).unwrap();
+
+ Ok(PalettedContainer {
+ bits_per_entry: 0,
+ palette,
+ storage,
+ container_type: *container_type,
+ })
+ }
+
pub fn read_with_type(
buf: &mut impl Read,
- type_: &'static PalettedContainerType,
+ container_type: &'static PalettedContainerType,
) -> Result<Self, BufReadError> {
let bits_per_entry = buf.read_byte()?;
- let palette = match type_ {
- PalettedContainerType::BlockStates => {
- Palette::block_states_read_with_bits_per_entry(buf, bits_per_entry)?
- }
- PalettedContainerType::Biomes => {
- Palette::biomes_read_with_bits_per_entry(buf, bits_per_entry)?
- }
- };
- let size = match type_ {
- PalettedContainerType::BlockStates => 4096,
- PalettedContainerType::Biomes => 64,
- };
+ let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type);
+ let palette = palette_type.read(buf)?;
+ let size = container_type.size();
let data = Vec::<u64>::read_from(buf)?;
debug_assert!(
@@ -50,24 +54,130 @@ impl PalettedContainer {
bits_per_entry,
palette,
storage,
- container_type: *type_,
+ container_type: *container_type,
})
}
+ /// Calculates the index of the given coordinates.
pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize {
- let size_bits = match self.container_type {
- PalettedContainerType::BlockStates => 4,
- PalettedContainerType::Biomes => 2,
- };
+ let size_bits = self.container_type.size_bits();
(((y << size_bits) | z) << size_bits) | x
}
- pub fn get(&self, x: usize, y: usize, z: usize) -> u32 {
- let paletted_value = self.storage.get(self.get_index(x, y, z));
- println!("palette: {:?}", self.palette);
+ /// Returns the value at the given index.
+ pub fn get_at_index(&self, index: usize) -> u32 {
+ let paletted_value = self.storage.get(index);
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) -> u32 {
+ // 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.get_index(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: u32) -> u32 {
+ let paletted_value = self.id_for(value);
+ self.storage
+ .get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32
+ }
+
+ /// 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: u32) {
+ 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: u32) {
+ self.set_at_index(self.get_index(x, y, z), value);
+ }
+
+ fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer {
+ let new_palette_type =
+ PaletteType::from_bits_and_type(bits_per_entry, &self.container_type);
+ // note for whoever is trying to optimize this: vanilla has this
+ // but it causes a stack overflow since it's not changing the bits per entry
+ // i don't know how to fix this properly so glhf
+ // let old_palette_type: PaletteType = (&self.palette).into();
+ // if 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.into_empty_palette(1usize << (bits_per_entry as usize));
+ let palette = new_palette_type.into_empty_palette();
+ PalettedContainer {
+ bits_per_entry,
+ palette,
+ storage,
+ container_type: self.container_type,
+ }
+ }
+
+ fn on_resize(&mut self, bits_per_entry: u8, value: u32) -> usize {
+ if bits_per_entry > 5 {
+ panic!("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;
+ let id = self.id_for(value);
+ id
+ }
+
+ 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: u32) -> 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 as usize;
+ }
+ 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 also i don't care
+ if let Some(index) = palette.iter().position(|v| *v == value) {
+ return index as usize;
+ }
+ 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 as usize,
+ }
+ }
}
impl McBufWritable for PalettedContainer {
@@ -79,45 +189,37 @@ impl McBufWritable for PalettedContainer {
}
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PaletteType {
+ SingleValue,
+ Linear,
+ Hashmap,
+ Global,
+}
+
#[derive(Clone, Debug)]
pub enum Palette {
/// ID of the corresponding entry in its global palette
SingleValue(u32),
+ // 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<u32>),
Hashmap(Vec<u32>),
Global,
}
impl Palette {
- pub fn block_states_read_with_bits_per_entry(
- buf: &mut impl Read,
- bits_per_entry: u8,
- ) -> Result<Palette, BufReadError> {
- Ok(match bits_per_entry {
- 0 => Palette::SingleValue(u32::var_read_from(buf)?),
- 1..=4 => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
- 5..=8 => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
- _ => Palette::Global,
- })
- }
-
- pub fn biomes_read_with_bits_per_entry(
- buf: &mut impl Read,
- bits_per_entry: u8,
- ) -> Result<Palette, BufReadError> {
- Ok(match bits_per_entry {
- 0 => Palette::SingleValue(u32::var_read_from(buf)?),
- 1..=3 => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
- _ => Palette::Global,
- })
- }
-
- pub fn value_for(&self, value: usize) -> u32 {
+ pub fn value_for(&self, id: usize) -> u32 {
match self {
Palette::SingleValue(v) => *v,
- Palette::Linear(v) => v[value],
- Palette::Hashmap(v) => v[value],
- Palette::Global => value as u32,
+ Palette::Linear(v) => v[id],
+ Palette::Hashmap(v) => {
+ if id >= v.len() {
+ 0
+ } else {
+ v[id]
+ }
+ }
+ Palette::Global => id as u32,
}
}
}
@@ -139,3 +241,123 @@ impl McBufWritable for Palette {
Ok(())
}
}
+
+impl PaletteType {
+ pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self {
+ match container_type {
+ PalettedContainerType::BlockStates => match bits_per_entry {
+ 0 => PaletteType::SingleValue,
+ 1..=4 => PaletteType::Linear,
+ 5..=8 => PaletteType::Hashmap,
+ _ => PaletteType::Global,
+ },
+ PalettedContainerType::Biomes => match bits_per_entry {
+ 0 => PaletteType::SingleValue,
+ 1..=3 => PaletteType::Linear,
+ _ => PaletteType::Global,
+ },
+ }
+ }
+
+ pub fn read(&self, buf: &mut impl Read) -> Result<Palette, BufReadError> {
+ Ok(match self {
+ PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
+ PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
+ PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
+ PaletteType::Global => Palette::Global,
+ })
+ }
+
+ pub fn into_empty_palette(&self) -> Palette {
+ match self {
+ PaletteType::SingleValue => Palette::SingleValue(0),
+ PaletteType::Linear => Palette::Linear(Vec::new()),
+ PaletteType::Hashmap => Palette::Hashmap(Vec::new()),
+ PaletteType::Global => Palette::Global,
+ }
+ }
+}
+
+impl From<&Palette> for PaletteType {
+ fn from(palette: &Palette) -> Self {
+ match palette {
+ Palette::SingleValue(_) => PaletteType::SingleValue,
+ Palette::Linear(_) => PaletteType::Linear,
+ Palette::Hashmap(_) => PaletteType::Hashmap,
+ Palette::Global => PaletteType::Global,
+ }
+ }
+}
+
+impl PalettedContainerType {
+ fn size_bits(&self) -> usize {
+ match self {
+ PalettedContainerType::BlockStates => 4,
+ PalettedContainerType::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(&PalettedContainerType::BlockStates).unwrap();
+
+ assert_eq!(palette_container.bits_per_entry, 0);
+ assert_eq!(palette_container.get_at_index(0), 0);
+ assert_eq!(
+ PaletteType::from(&palette_container.palette),
+ PaletteType::SingleValue
+ );
+ palette_container.set_at_index(0, 1);
+ assert_eq!(palette_container.get_at_index(0), 1);
+ assert_eq!(
+ PaletteType::from(&palette_container.palette),
+ PaletteType::Linear
+ );
+ }
+
+ #[test]
+ fn test_resize_0_bits_to_5() {
+ let mut palette_container =
+ PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap();
+
+ palette_container.set_at_index(0, 0); // 0 bits
+ assert_eq!(palette_container.bits_per_entry, 0);
+
+ palette_container.set_at_index(1, 1); // 1 bit
+ assert_eq!(palette_container.bits_per_entry, 1);
+
+ palette_container.set_at_index(2, 2); // 2 bits
+ assert_eq!(palette_container.bits_per_entry, 2);
+ palette_container.set_at_index(3, 3);
+
+ palette_container.set_at_index(4, 4); // 3 bits
+ assert_eq!(palette_container.bits_per_entry, 3);
+ palette_container.set_at_index(5, 5);
+ palette_container.set_at_index(6, 6);
+ palette_container.set_at_index(7, 7);
+
+ palette_container.set_at_index(8, 8); // 4 bits
+ assert_eq!(palette_container.bits_per_entry, 4);
+ palette_container.set_at_index(9, 9);
+ palette_container.set_at_index(10, 10);
+ palette_container.set_at_index(11, 11);
+ palette_container.set_at_index(12, 12);
+ palette_container.set_at_index(13, 13);
+ palette_container.set_at_index(14, 14);
+ palette_container.set_at_index(15, 15);
+ assert_eq!(palette_container.bits_per_entry, 4);
+
+ palette_container.set_at_index(16, 16); // 5 bits
+ assert_eq!(palette_container.bits_per_entry, 5);
+ }
+}