From 60bfdf62db1ccd673ad0a84f152a241537f2e394 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 10 May 2026 12:13:33 +0200 Subject: [PATCH] Revert a55c42c83 --- Cargo.lock | 2 - pumpkin-data/src/data_component_impl.rs | 83 +- pumpkin-data/src/item_stack/mod.rs | 51 +- pumpkin-nbt/Cargo.toml | 6 - pumpkin-nbt/README.md | 22 - pumpkin-nbt/benches/nbt_bench.rs | 104 --- pumpkin-nbt/src/lib.rs | 2 - pumpkin-nbt/src/pnbt.rs | 907 ------------------- pumpkin-world/src/chunk/format/mod.rs | 57 +- pumpkin-world/src/chunk/format/pump.rs | 16 +- pumpkin-world/src/chunk/mod.rs | 4 +- pumpkin-world/src/data/player_data.rs | 58 +- pumpkin-world/src/world_info/anvil.rs | 71 +- pumpkin/src/command/commands/data.rs | 17 +- pumpkin/src/data/player_server.rs | 44 +- pumpkin/src/entity/decoration/armor_stand.rs | 112 +-- pumpkin/src/entity/decoration/painting.rs | 11 +- pumpkin/src/entity/effect/mod.rs | 52 +- pumpkin/src/entity/hunger.rs | 27 +- pumpkin/src/entity/living.rs | 53 +- pumpkin/src/entity/mob/bat.rs | 11 +- pumpkin/src/entity/mob/creeper.rs | 39 +- pumpkin/src/entity/mob/enderman.rs | 15 +- pumpkin/src/entity/mob/shulker.rs | 18 +- pumpkin/src/entity/mob/skeleton/mod.rs | 6 +- pumpkin/src/entity/mob/slime.rs | 21 +- pumpkin/src/entity/mob/zombie/mod.rs | 6 +- pumpkin/src/entity/mod.rs | 112 ++- pumpkin/src/entity/passive/chicken.rs | 10 +- pumpkin/src/entity/passive/cow.rs | 6 +- pumpkin/src/entity/passive/iron_golem.rs | 6 +- pumpkin/src/entity/passive/pig.rs | 6 +- pumpkin/src/entity/passive/sheep.rs | 23 +- pumpkin/src/entity/passive/villager/mod.rs | 166 ++-- pumpkin/src/entity/player.rs | 296 +++--- pumpkin/src/server/mod.rs | 13 +- pumpkin/src/world/mod.rs | 112 +-- pumpkin/src/world/natural_spawner.rs | 4 +- 38 files changed, 748 insertions(+), 1821 deletions(-) delete mode 100644 pumpkin-nbt/README.md delete mode 100644 pumpkin-nbt/benches/nbt_bench.rs delete mode 100644 pumpkin-nbt/src/pnbt.rs diff --git a/Cargo.lock b/Cargo.lock index 17592153e..9aa498e02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2953,14 +2953,12 @@ version = "0.1.0-dev+26.1" dependencies = [ "bytes", "cesu8", - "criterion", "flate2", "pumpkin-codecs", "serde", "tempfile", "thiserror 2.0.18", "tracing", - "uuid", ] [[package]] diff --git a/pumpkin-data/src/data_component_impl.rs b/pumpkin-data/src/data_component_impl.rs index 10dccbf65..81705233b 100644 --- a/pumpkin-data/src/data_component_impl.rs +++ b/pumpkin-data/src/data_component_impl.rs @@ -32,13 +32,10 @@ use std::{ hash::{Hash, Hasher}, }; -use pumpkin_nbt::pnbt::PNbtCompound; - pub trait DataComponentImpl: Send + Sync { fn write_data(&self) -> NbtTag { NbtTag::End } - fn write_data_pnbt(&self, _nbt: &mut PNbtCompound) {} fn get_hash(&self) -> i32 { todo!() } @@ -73,21 +70,6 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option None, } } - -#[must_use] -pub fn read_data_pnbt( - id: DataComponent, - nbt: &mut PNbtCompound, -) -> Option> { - match id { - MaxStackSize => Some(MaxStackSizeImpl::read_data_pnbt(nbt)?.to_dyn()), - Enchantments => Some(EnchantmentsImpl::read_data_pnbt(nbt)?.to_dyn()), - Damage => Some(DamageImpl::read_data_pnbt(nbt)?.to_dyn()), - Unbreakable => Some(UnbreakableImpl::read_data_pnbt(nbt)?.to_dyn()), - MapId => Some(MapIdImpl::read_data_pnbt(nbt)?.to_dyn()), - _ => None, - } -} // Also Pumpkin\pumpkin-protocol\src\codec\data_component.rs macro_rules! default_impl { @@ -154,18 +136,11 @@ impl MaxStackSizeImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|size| Self { size: size as u8 }) } - - fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { - nbt.get_u8().ok().map(|size| Self { size }) - } } impl DataComponentImpl for MaxStackSizeImpl { fn write_data(&self) -> NbtTag { NbtTag::Int(i32::from(self.size)) } - fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { - nbt.put_u8(self.size); - } fn get_hash(&self) -> i32 { get_i32_hash(i32::from(self.size)) as i32 } @@ -187,19 +162,11 @@ impl DamageImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|damage| Self { damage }) } - - fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { - nbt.get_int().ok().map(|damage| Self { damage }) - } } impl DataComponentImpl for DamageImpl { fn write_data(&self) -> NbtTag { NbtTag::Int(self.damage) } - - fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { - nbt.put_int(self.damage); - } fn get_hash(&self) -> i32 { get_i32_hash(self.damage) as i32 } @@ -211,16 +178,11 @@ impl UnbreakableImpl { const fn read_data(_data: &NbtTag) -> Option { Some(Self) } - - fn read_data_pnbt(_nbt: &mut PNbtCompound) -> Option { - Some(Self) - } } impl DataComponentImpl for UnbreakableImpl { fn write_data(&self) -> NbtTag { NbtTag::Compound(NbtCompound::new()) } - fn write_data_pnbt(&self, _nbt: &mut PNbtCompound) {} fn get_hash(&self) -> i32 { 0 } @@ -291,19 +253,6 @@ impl EnchantmentsImpl { enchantment: Cow::from(enc), }) } - - fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { - let len = nbt.get_u32().ok()? as usize; - let mut enc = Vec::with_capacity(len); - for _ in 0..len { - let name = nbt.get_string().ok()?; - let level = nbt.get_int().ok()?; - enc.push((Enchantment::from_name(name.as_str())?, level)); - } - Some(Self { - enchantment: Cow::from(enc), - }) - } } fn get_str_hash(val: &str) -> u32 { @@ -421,21 +370,12 @@ fn hash() { impl DataComponentImpl for EnchantmentsImpl { fn write_data(&self) -> NbtTag { - let mut compound = NbtCompound::new(); + let mut data = NbtCompound::new(); for (enc, level) in self.enchantment.iter() { - compound.put_int(enc.name, *level); + data.put_int(enc.name, *level); } - NbtTag::Compound(compound) + NbtTag::Compound(data) } - - fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { - nbt.put_u32(self.enchantment.len() as u32); - for (enc, level) in self.enchantment.iter() { - nbt.put_string(enc.name); - nbt.put_int(*level); - } - } - fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); @@ -1153,18 +1093,15 @@ impl DataComponentImpl for WeaponImpl { default_impl!(Weapon); } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum EquipmentType { - #[default] - Any, Hand, HumanoidArmor, AnimalArmor, - Body, Saddle, } -#[derive(Clone, Hash, Eq, PartialEq, Default)] +#[derive(Clone, Hash, Eq, PartialEq)] pub struct EquipmentSlotData { pub slot_type: EquipmentType, pub entity_id: i32, @@ -1230,7 +1167,7 @@ impl EquipmentSlot { name: Cow::Borrowed("head"), }); pub const BODY: Self = Self::Body(EquipmentSlotData { - slot_type: EquipmentType::Body, + slot_type: EquipmentType::AnimalArmor, entity_id: 0, index: 6, max_count: 1, @@ -1568,10 +1505,6 @@ impl MapIdImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|id| Self { id }) } - - fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { - nbt.get_i32().ok().map(|id| Self { id }) - } } impl DataComponentImpl for MapIdImpl { @@ -1579,10 +1512,6 @@ impl DataComponentImpl for MapIdImpl { NbtTag::Int(self.id) } - fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { - nbt.put_i32(self.id); - } - fn get_hash(&self) -> i32 { get_i32_hash(self.id) as i32 } diff --git a/pumpkin-data/src/item_stack/mod.rs b/pumpkin-data/src/item_stack/mod.rs index 91e71c2cd..1b6e53335 100644 --- a/pumpkin-data/src/item_stack/mod.rs +++ b/pumpkin-data/src/item_stack/mod.rs @@ -3,14 +3,13 @@ use crate::data_component::DataComponent::Enchantments; use crate::data_component_impl::{ BlocksAttacksImpl, ConsumableImpl, DamageImpl, DataComponentImpl, EnchantmentsImpl, IDSet, MaxDamageImpl, MaxStackSizeImpl, ToolImpl, UnbreakableImpl, UseCooldownImpl, get, get_mut, - read_data, read_data_pnbt, + read_data, }; use crate::item::Item; use crate::recipes::RecipeResultStruct; use crate::tag::Taggable; use crate::{Block, Enchantment}; use pumpkin_nbt::compound::NbtCompound; -use pumpkin_nbt::pnbt::PNbtCompound; use pumpkin_util::GameMode; use rand; use std::borrow::Cow; @@ -537,25 +536,6 @@ impl ItemStack { compound.put_compound("components", tag); } - pub fn write_item_stack_pnbt(&self, nbt: &mut PNbtCompound) { - // Positional: write ID as string, then count as u8 - nbt.put_string(&format!("minecraft:{}", self.item.registry_key)); - nbt.put_u8(self.item_count); - - // Positional: write number of components - nbt.put_u32(self.patch.len() as u32); - for (id, data) in &self.patch { - // Write component ID as u8 (matching DataComponent enum) - nbt.put_u8(*id as u8); - if let Some(data) = data { - nbt.put_bool(true); // Is presence - data.write_data_pnbt(nbt); - } else { - nbt.put_bool(false); // Is deletion (!) - } - } - } - #[must_use] pub fn read_item_stack(compound: &NbtCompound) -> Option { // Get ID, which is a string like "minecraft:diamond_sword" @@ -588,35 +568,6 @@ impl ItemStack { Some(item_stack) } - - #[must_use] - pub fn read_item_stack_pnbt(nbt: &mut PNbtCompound) -> Option { - // Read ID as string - let full_id = nbt.get_string().ok()?; - let registry_key = full_id.strip_prefix("minecraft:").unwrap_or(&full_id); - let item = Item::from_registry_key(registry_key)?; - - // Read count as u8 - let count = nbt.get_u8().ok()?; - - let mut item_stack = Self::new(count, item); - - // Read components - let patch_len = nbt.get_u32().ok()? as usize; - for _ in 0..patch_len { - let component_id_raw = nbt.get_u8().ok()?; - let id = DataComponent::try_from_id(component_id_raw)?; - let is_present = nbt.get_bool().ok()?; - - if is_present { - item_stack.patch.push((id, Some(read_data_pnbt(id, nbt)?))); - } else { - item_stack.patch.push((id, None)); - } - } - - Some(item_stack) - } } impl From<&RecipeResultStruct> for ItemStack { diff --git a/pumpkin-nbt/Cargo.toml b/pumpkin-nbt/Cargo.toml index 29de13cf8..50182eec9 100644 --- a/pumpkin-nbt/Cargo.toml +++ b/pumpkin-nbt/Cargo.toml @@ -10,7 +10,6 @@ pumpkin-codecs.workspace = true serde.workspace = true thiserror.workspace = true bytes.workspace = true -uuid.workspace = true cesu8.workspace = true flate2.workspace = true @@ -18,11 +17,6 @@ tracing.workspace = true [dev-dependencies] tempfile.workspace = true -criterion.workspace = true - -[[bench]] -name = "nbt_bench" -harness = false [lints] workspace = true diff --git a/pumpkin-nbt/README.md b/pumpkin-nbt/README.md deleted file mode 100644 index 9b13ae027..000000000 --- a/pumpkin-nbt/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# PNBT Specification - -PNBT is a high-speed, positional binary format designed for maximum storage efficiency and extreme serialization/deserialization throughput. Unlike standard NBT, PNBT is **positional** and does not store field names or tag IDs in the stream, making it ideal for internal storage (player data, level metadata) where the schema is stable. - -### Key Features -- **Zero Overhead:** No string keys or tag IDs stored in the binary stream. -- **ZigZag Varints:** Uses LEB128 encoding for all integers and lengths, with ZigZag for signed types. -- **Zero-Copy Deserialization:** Directly borrows strings and bytes from the input buffer. -- **Extreme Performance:** Specifically optimized for high-frequency internal data storage. - -### Binary Layout -PNBT follows a strict positional layout defined by the Rust struct being serialized: -- **Primitives:** LEB128/ZigZag varints for integers. Fixed size for floats. -- **Strings/Bytes:** Varint length followed by raw payload. -- **Sequences/Maps:** Varint length followed by positional elements. - -## Performance (vs Vanilla NBT) -- **Size Efficiency:** **~43% to 47% smaller** footprint. -- **Serialization Speed:** **~8x faster** than vanilla NBT (554 ns vs 4.51 µs). -- **Deserialization Speed:** **~3x faster** than vanilla NBT (3.41 µs vs 9.92 µs). - -Note: Because PNBT is positional, any changes to the struct layout (adding/removing/reordering fields) will make existing serialized data incompatible unless handled manually (e.g. via `Option` or versioned structs). diff --git a/pumpkin-nbt/benches/nbt_bench.rs b/pumpkin-nbt/benches/nbt_bench.rs deleted file mode 100644 index 3694cb8c7..000000000 --- a/pumpkin-nbt/benches/nbt_bench.rs +++ /dev/null @@ -1,104 +0,0 @@ -#![allow(clippy::print_stdout)] - -use criterion::{Criterion, criterion_group, criterion_main}; -use pumpkin_nbt::{from_bytes_unnamed, from_pnbt, to_bytes_unnamed, to_pnbt}; -use serde::{Deserialize, Serialize}; -use std::hint::black_box; -use std::io::Cursor; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -struct LargeData { - id: i32, - name: String, - metadata: Vec, - inventory: Vec, - scores: Vec, - active: bool, - position: (f64, f64, f64), -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -struct Metadata { - key: String, - value: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -struct Item { - id: String, - count: i8, - slot: i32, -} - -fn create_large_data() -> LargeData { - LargeData { - id: 1234567, - name: "Pumpkin King".to_string(), - metadata: (0..50) - .map(|i| Metadata { - key: format!("meta_key_{i}"), - value: format!("meta_value_{i}"), - }) - .collect(), - inventory: (0..27) - .map(|i| Item { - id: "minecraft:diamond_sword".to_string(), - count: 64, - slot: i, - }) - .collect(), - scores: (0..100).map(|i| i * 1000).collect(), - active: true, - position: (1234.56, 64.0, -789.12), - } -} - -fn bench_nbt(c: &mut Criterion) { - let data = create_large_data(); - - // Size comparison - let mut vanilla_bytes = Vec::new(); - to_bytes_unnamed(&data, &mut vanilla_bytes).unwrap(); - let pnbt_bytes = to_pnbt(&data).unwrap(); - - println!("\nSize Comparison (LargeData):"); - println!("Vanilla NBT size: {} bytes", vanilla_bytes.len()); - println!("PNBT size: {} bytes", pnbt_bytes.len()); - println!( - "Reduction: {:.2}%\n", - (1.0 - (pnbt_bytes.len() as f64 / vanilla_bytes.len() as f64)) * 100.0 - ); - - let mut group = c.benchmark_group("NBT Comparison"); - - group.bench_function("Vanilla Serialize", |b| { - b.iter(|| { - let mut out = Vec::with_capacity(vanilla_bytes.len()); - to_bytes_unnamed(black_box(&data), &mut out).unwrap(); - }); - }); - - group.bench_function("PNBT Serialize", |b| { - b.iter(|| { - to_pnbt(black_box(&data)).unwrap(); - }); - }); - - group.bench_function("Vanilla Deserialize", |b| { - b.iter(|| { - let cursor = Cursor::new(&vanilla_bytes); - let _: LargeData = from_bytes_unnamed(cursor).unwrap(); - }); - }); - - group.bench_function("PNBT Deserialize", |b| { - b.iter(|| { - let _: LargeData = from_pnbt(black_box(&pnbt_bytes)).unwrap(); - }); - }); - - group.finish(); -} - -criterion_group!(benches, bench_nbt); -criterion_main!(benches); diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index 9b6571239..6e771c3b1 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -16,12 +16,10 @@ pub mod compound; pub mod deserializer; pub mod nbt_compress; pub mod nbt_ops; -pub mod pnbt; pub mod serializer; pub mod tag; pub use deserializer::{from_bytes, from_bytes_unnamed}; -pub use pnbt::{from_pnbt, to_pnbt}; pub use serializer::{to_bytes, to_bytes_named, to_bytes_unnamed}; // This NBT crate is inspired from CrabNBT diff --git a/pumpkin-nbt/src/pnbt.rs b/pumpkin-nbt/src/pnbt.rs deleted file mode 100644 index 104ec0b0f..000000000 --- a/pumpkin-nbt/src/pnbt.rs +++ /dev/null @@ -1,907 +0,0 @@ -use crate::{Error, NBT_ARRAY_TAG, NBT_BYTE_ARRAY_TAG, NBT_INT_ARRAY_TAG, NBT_LONG_ARRAY_TAG}; -use serde::{ - Deserialize, Serialize, - de::{self, MapAccess, SeqAccess, Visitor}, - ser, -}; - -/// Serializes struct to PNBT (Pumpkin NBT) format. -/// PNBT is a high-performance, positional binary format. -#[inline] -pub fn to_pnbt(value: &T) -> Result, Error> { - let mut serializer = Serializer::new(); - value.serialize(&mut serializer)?; - Ok(serializer.output) -} - -/// Deserializes struct from PNBT format. -#[inline] -pub fn from_pnbt<'a, T: Deserialize<'a>>(input: &'a [u8]) -> Result { - let mut deserializer = Deserializer::new(input); - T::deserialize(&mut deserializer) -} - -/// `PNbtCompound` is a direct byte-wrapper for building or reading PNBT data. -/// It provides a manual Protobuf-like API without string keys. -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct PNbtCompound { - pub data: Vec, - pub read_pos: usize, -} - -impl PNbtCompound { - #[must_use] - pub fn new() -> Self { - Self { - data: Vec::with_capacity(1024), - read_pos: 0, - } - } - - #[must_use] - pub const fn from_bytes(data: Vec) -> Self { - Self { data, read_pos: 0 } - } - - #[must_use] - pub fn into_bytes(self) -> Vec { - self.data - } - - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - &self.data - } - - // --- Writing API --- - - pub fn put_bool(&mut self, v: bool) { - self.data.push(u8::from(v)); - } - - pub fn put_i8(&mut self, v: i8) { - self.data.push(v as u8); - } - - /// Alias for `put_i8` - pub fn put_byte(&mut self, v: i8) { - self.put_i8(v); - } - - pub fn put_u8(&mut self, v: u8) { - self.data.push(v); - } - - fn write_varint(&mut self, mut value: u64) { - loop { - let byte = (value & 0x7f) as u8; - value >>= 7; - if value == 0 { - self.data.push(byte); - break; - } - self.data.push(byte | 0x80); - } - } - - fn write_zigzag(&mut self, value: i64) { - let encoded = ((value << 1) ^ (value >> 63)) as u64; - self.write_varint(encoded); - } - - pub fn put_i16(&mut self, v: i16) { - self.write_zigzag(i64::from(v)); - } - - /// Alias for `put_i16` - pub fn put_short(&mut self, v: i16) { - self.put_i16(v); - } - - pub fn put_u16(&mut self, v: u16) { - self.write_varint(u64::from(v)); - } - - pub fn put_i32(&mut self, v: i32) { - self.write_zigzag(i64::from(v)); - } - - /// Alias for `put_i32` - pub fn put_int(&mut self, v: i32) { - self.put_i32(v); - } - - pub fn put_u32(&mut self, v: u32) { - self.write_varint(u64::from(v)); - } - - pub fn put_i64(&mut self, v: i64) { - self.write_zigzag(v); - } - - /// Alias for `put_i64` - pub fn put_long(&mut self, v: i64) { - self.put_i64(v); - } - - pub fn put_u64(&mut self, v: u64) { - self.write_varint(v); - } - - pub fn put_f32(&mut self, v: f32) { - self.data.extend_from_slice(&v.to_le_bytes()); - } - - /// Alias for `put_f32` - pub fn put_float(&mut self, v: f32) { - self.put_f32(v); - } - - pub fn put_f64(&mut self, v: f64) { - self.data.extend_from_slice(&v.to_le_bytes()); - } - - /// Alias for `put_f64` - pub fn put_double(&mut self, v: f64) { - self.put_f64(v); - } - - pub fn put_string(&mut self, v: &str) { - self.write_varint(v.len() as u64); - self.data.extend_from_slice(v.as_bytes()); - } - - pub fn put_bytes(&mut self, v: &[u8]) { - self.write_varint(v.len() as u64); - self.data.extend_from_slice(v); - } - - pub fn put_uuid(&mut self, v: &uuid::Uuid) { - self.data.extend_from_slice(v.as_bytes()); - } - - // --- Reading API --- - - fn read_byte(&mut self) -> Result { - if self.read_pos >= self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let b = self.data[self.read_pos]; - self.read_pos += 1; - Ok(b) - } - - fn read_varint(&mut self) -> Result { - let mut value = 0; - let mut shift = 0; - loop { - let byte = self.read_byte()?; - value |= ((byte & 0x7f) as u64) << shift; - if byte & 0x80 == 0 { - break; - } - shift += 7; - } - Ok(value) - } - - fn read_zigzag(&mut self) -> Result { - let value = self.read_varint()?; - Ok(((value >> 1) as i64) ^ (-((value & 1) as i64))) - } - - pub fn get_bool(&mut self) -> Result { - Ok(self.read_byte()? != 0) - } - - pub fn get_i8(&mut self) -> Result { - Ok(self.read_byte()? as i8) - } - - /// Alias for `get_i8` - pub fn get_byte(&mut self) -> Result { - self.get_i8() - } - - pub fn get_u8(&mut self) -> Result { - self.read_byte() - } - - pub fn get_i16(&mut self) -> Result { - Ok(self.read_zigzag()? as i16) - } - - /// Alias for `get_i16` - pub fn get_short(&mut self) -> Result { - self.get_i16() - } - - pub fn get_u16(&mut self) -> Result { - Ok(self.read_varint()? as u16) - } - - pub fn get_i32(&mut self) -> Result { - Ok(self.read_zigzag()? as i32) - } - - /// Alias for `get_i32` - pub fn get_int(&mut self) -> Result { - self.get_i32() - } - - pub fn get_u32(&mut self) -> Result { - Ok(self.read_varint()? as u32) - } - - pub fn get_i64(&mut self) -> Result { - self.read_zigzag() - } - - /// Alias for `get_i64` - pub fn get_long(&mut self) -> Result { - self.get_i64() - } - - pub fn get_u64(&mut self) -> Result { - self.read_varint() - } - - pub fn get_f32(&mut self) -> Result { - if self.read_pos + 4 > self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let mut b = [0u8; 4]; - b.copy_from_slice(&self.data[self.read_pos..self.read_pos + 4]); - self.read_pos += 4; - Ok(f32::from_le_bytes(b)) - } - - /// Alias for `get_f32` - pub fn get_float(&mut self) -> Result { - self.get_f32() - } - - pub fn get_f64(&mut self) -> Result { - if self.read_pos + 8 > self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let mut b = [0u8; 8]; - b.copy_from_slice(&self.data[self.read_pos..self.read_pos + 8]); - self.read_pos += 8; - Ok(f64::from_le_bytes(b)) - } - - /// Alias for `get_f64` - pub fn get_double(&mut self) -> Result { - self.get_f64() - } - - pub fn get_string(&mut self) -> Result { - let len = self.read_varint()? as usize; - if self.read_pos + len > self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let s = std::str::from_utf8(&self.data[self.read_pos..self.read_pos + len]) - .map_err(|e| Error::SerdeError(e.to_string()))?; - self.read_pos += len; - Ok(s.to_string()) - } - - pub fn get_bytes(&mut self) -> Result, Error> { - let len = self.read_varint()? as usize; - if self.read_pos + len > self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let b = self.data[self.read_pos..self.read_pos + len].to_vec(); - self.read_pos += len; - Ok(b) - } - - pub fn get_uuid(&mut self) -> Result { - if self.read_pos + 16 > self.data.len() { - return Err(Error::SerdeError("EOF".to_string())); - } - let mut b = [0u8; 16]; - b.copy_from_slice(&self.data[self.read_pos..self.read_pos + 16]); - self.read_pos += 16; - Ok(uuid::Uuid::from_bytes(b)) - } -} - -pub struct Serializer { - output: Vec, -} - -impl Serializer { - #[must_use] - #[inline] - pub fn new() -> Self { - Self { - output: Vec::with_capacity(1024), - } - } - - #[inline] - fn write_varint(&mut self, mut value: u64) { - loop { - let byte = (value & 0x7f) as u8; - value >>= 7; - if value == 0 { - self.output.push(byte); - break; - } - self.output.push(byte | 0x80); - } - } - - #[inline] - fn write_zigzag(&mut self, value: i64) { - let encoded = ((value << 1) ^ (value >> 63)) as u64; - self.write_varint(encoded); - } -} - -impl Default for Serializer { - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl ser::Serializer for &mut Serializer { - type Ok = (); - type Error = Error; - type SerializeSeq = Self; - type SerializeTuple = Self; - type SerializeTupleStruct = Self; - type SerializeTupleVariant = Self; - type SerializeMap = Self; - type SerializeStruct = Self; - type SerializeStructVariant = Self; - - #[inline] - fn serialize_bool(self, v: bool) -> Result<(), Error> { - self.output.push(u8::from(v)); - Ok(()) - } - #[inline] - fn serialize_i8(self, v: i8) -> Result<(), Error> { - self.output.push(v as u8); - Ok(()) - } - #[inline] - fn serialize_i16(self, v: i16) -> Result<(), Error> { - self.write_zigzag(i64::from(v)); - Ok(()) - } - #[inline] - fn serialize_i32(self, v: i32) -> Result<(), Error> { - self.write_zigzag(i64::from(v)); - Ok(()) - } - #[inline] - fn serialize_i64(self, v: i64) -> Result<(), Error> { - self.write_zigzag(v); - Ok(()) - } - fn serialize_u8(self, v: u8) -> Result<(), Error> { - self.output.push(v); - Ok(()) - } - fn serialize_u16(self, v: u16) -> Result<(), Error> { - self.write_varint(u64::from(v)); - Ok(()) - } - fn serialize_u32(self, v: u32) -> Result<(), Error> { - self.write_varint(u64::from(v)); - Ok(()) - } - fn serialize_u64(self, v: u64) -> Result<(), Error> { - self.write_varint(v); - Ok(()) - } - #[inline] - fn serialize_f32(self, v: f32) -> Result<(), Error> { - self.output.extend_from_slice(&v.to_le_bytes()); - Ok(()) - } - #[inline] - fn serialize_f64(self, v: f64) -> Result<(), Error> { - self.output.extend_from_slice(&v.to_le_bytes()); - Ok(()) - } - fn serialize_char(self, v: char) -> Result<(), Error> { - self.serialize_str(&v.to_string()) - } - #[inline] - fn serialize_str(self, v: &str) -> Result<(), Error> { - self.write_varint(v.len() as u64); - self.output.extend_from_slice(v.as_bytes()); - Ok(()) - } - #[inline] - fn serialize_bytes(self, v: &[u8]) -> Result<(), Error> { - self.write_varint(v.len() as u64); - self.output.extend_from_slice(v); - Ok(()) - } - fn serialize_none(self) -> Result<(), Error> { - self.output.push(0); - Ok(()) - } - fn serialize_some(self, value: &T) -> Result<(), Error> { - self.output.push(1); - value.serialize(self) - } - fn serialize_unit(self) -> Result<(), Error> { - Ok(()) - } - fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> { - Ok(()) - } - fn serialize_unit_variant( - self, - _name: &'static str, - idx: u32, - _variant: &'static str, - ) -> Result<(), Error> { - self.write_varint(u64::from(idx)); - Ok(()) - } - fn serialize_newtype_struct( - self, - _name: &'static str, - value: &T, - ) -> Result<(), Error> { - value.serialize(self) - } - fn serialize_newtype_variant( - self, - name: &'static str, - idx: u32, - variant: &'static str, - value: &T, - ) -> Result<(), Error> { - if name == NBT_ARRAY_TAG { - match variant { - NBT_BYTE_ARRAY_TAG | NBT_INT_ARRAY_TAG | NBT_LONG_ARRAY_TAG => { - // Positional: skip indices/tags, just write data - return value.serialize(self); - } - _ => {} - } - } - self.write_varint(u64::from(idx)); - value.serialize(self) - } - #[inline] - fn serialize_seq(self, len: Option) -> Result { - let len = len.ok_or_else(|| Error::SerdeError("Length required".to_string()))?; - self.write_varint(len as u64); - Ok(self) - } - fn serialize_tuple(self, _len: usize) -> Result { - Ok(self) - } - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } - #[inline] - fn serialize_map(self, len: Option) -> Result { - let len = len.ok_or_else(|| Error::SerdeError("Length required".to_string()))?; - self.write_varint(len as u64); - Ok(self) - } - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } -} - -impl ser::SerializeSeq for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeTuple for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeTupleStruct for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeTupleVariant for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeStruct for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_field( - &mut self, - _key: &'static str, - value: &T, - ) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeStructVariant for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_field( - &mut self, - _key: &'static str, - value: &T, - ) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} -impl ser::SerializeMap for &mut Serializer { - type Ok = (); - type Error = Error; - fn serialize_key(&mut self, key: &T) -> Result<(), Error> { - key.serialize(&mut **self) - } - fn serialize_value(&mut self, value: &T) -> Result<(), Error> { - value.serialize(&mut **self) - } - fn end(self) -> Result<(), Error> { - Ok(()) - } -} - -pub struct Deserializer<'de> { - input: &'de [u8], -} - -impl<'de> Deserializer<'de> { - #[must_use] - pub const fn new(input: &'de [u8]) -> Self { - Self { input } - } - fn read_byte(&mut self) -> Result { - if self.input.is_empty() { - return Err(Error::SerdeError("EOF".to_string())); - } - let b = self.input[0]; - self.input = &self.input[1..]; - Ok(b) - } - fn read_varint(&mut self) -> Result { - let mut value = 0; - let mut shift = 0; - loop { - let byte = self.read_byte()?; - value |= ((byte & 0x7f) as u64) << shift; - if byte & 0x80 == 0 { - break; - } - shift += 7; - } - Ok(value) - } - fn read_zigzag(&mut self) -> Result { - let value = self.read_varint()?; - Ok(((value >> 1) as i64) ^ (-((value & 1) as i64))) - } -} - -impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { - type Error = Error; - fn deserialize_any>(self, _visitor: V) -> Result { - Err(Error::SerdeError("Positional PNBT needs types".to_string())) - } - fn deserialize_bool>(self, visitor: V) -> Result { - visitor.visit_bool(self.read_byte()? != 0) - } - fn deserialize_i8>(self, visitor: V) -> Result { - visitor.visit_i8(self.read_byte()? as i8) - } - fn deserialize_i16>(self, visitor: V) -> Result { - visitor.visit_i16(self.read_zigzag()? as i16) - } - fn deserialize_i32>(self, visitor: V) -> Result { - visitor.visit_i32(self.read_zigzag()? as i32) - } - fn deserialize_i64>(self, visitor: V) -> Result { - visitor.visit_i64(self.read_zigzag()?) - } - fn deserialize_u8>(self, visitor: V) -> Result { - visitor.visit_u8(self.read_byte()?) - } - fn deserialize_u16>(self, visitor: V) -> Result { - visitor.visit_u16(self.read_varint()? as u16) - } - fn deserialize_u32>(self, visitor: V) -> Result { - visitor.visit_u32(self.read_varint()? as u32) - } - fn deserialize_u64>(self, visitor: V) -> Result { - visitor.visit_u64(self.read_varint()?) - } - fn deserialize_f32>(self, visitor: V) -> Result { - if self.input.len() < 4 { - return Err(Error::SerdeError("EOF".to_string())); - } - let mut b = [0u8; 4]; - b.copy_from_slice(&self.input[..4]); - self.input = &self.input[4..]; - visitor.visit_f32(f32::from_le_bytes(b)) - } - fn deserialize_f64>(self, visitor: V) -> Result { - if self.input.len() < 8 { - return Err(Error::SerdeError("EOF".to_string())); - } - let mut b = [0u8; 8]; - b.copy_from_slice(&self.input[..8]); - self.input = &self.input[8..]; - visitor.visit_f64(f64::from_le_bytes(b)) - } - fn deserialize_char>(self, visitor: V) -> Result { - self.deserialize_str(visitor) - } - fn deserialize_str>(self, visitor: V) -> Result { - let len = self.read_varint()? as usize; - if self.input.len() < len { - return Err(Error::SerdeError("EOF".to_string())); - } - let s = std::str::from_utf8(&self.input[..len]) - .map_err(|e| Error::SerdeError(e.to_string()))?; - self.input = &self.input[len..]; - visitor.visit_borrowed_str(s) - } - fn deserialize_string>(self, visitor: V) -> Result { - self.deserialize_str(visitor) - } - fn deserialize_bytes>(self, visitor: V) -> Result { - let len = self.read_varint()? as usize; - if self.input.len() < len { - return Err(Error::SerdeError("EOF".to_string())); - } - let b = &self.input[..len]; - self.input = &self.input[len..]; - visitor.visit_borrowed_bytes(b) - } - fn deserialize_byte_buf>(self, visitor: V) -> Result { - self.deserialize_bytes(visitor) - } - fn deserialize_option>(self, visitor: V) -> Result { - if self.read_byte()? == 0 { - visitor.visit_none() - } else { - visitor.visit_some(self) - } - } - fn deserialize_unit>(self, visitor: V) -> Result { - visitor.visit_unit() - } - fn deserialize_unit_struct>( - self, - _name: &'static str, - visitor: V, - ) -> Result { - visitor.visit_unit() - } - fn deserialize_newtype_struct>( - self, - _name: &'static str, - visitor: V, - ) -> Result { - visitor.visit_newtype_struct(self) - } - fn deserialize_seq>(self, visitor: V) -> Result { - let len = self.read_varint()? as usize; - visitor.visit_seq(RawSeq { de: self, len }) - } - fn deserialize_tuple>(self, len: usize, visitor: V) -> Result { - visitor.visit_seq(RawSeq { de: self, len }) - } - fn deserialize_tuple_struct>( - self, - _name: &'static str, - len: usize, - visitor: V, - ) -> Result { - visitor.visit_seq(RawSeq { de: self, len }) - } - fn deserialize_map>(self, visitor: V) -> Result { - let len = self.read_varint()? as usize; - visitor.visit_map(RawMap { de: self, len }) - } - fn deserialize_struct>( - self, - _name: &'static str, - fields: &'static [&'static str], - visitor: V, - ) -> Result { - visitor.visit_seq(RawSeq { - de: self, - len: fields.len(), - }) - } - fn deserialize_enum>( - self, - _name: &'static str, - _variants: &'static [&'static str], - _visitor: V, - ) -> Result { - Err(Error::SerdeError("Unimplemented".to_string())) - } - fn deserialize_identifier>(self, visitor: V) -> Result { - self.deserialize_str(visitor) - } - fn deserialize_ignored_any>(self, visitor: V) -> Result { - visitor.visit_unit() - } -} - -struct RawSeq<'a, 'de> { - de: &'a mut Deserializer<'de>, - len: usize, -} -impl<'de> SeqAccess<'de> for RawSeq<'_, 'de> { - type Error = Error; - fn next_element_seed>( - &mut self, - seed: E, - ) -> Result, Error> { - if self.len == 0 { - return Ok(None); - } - self.len -= 1; - seed.deserialize(&mut *self.de).map(Some) - } -} - -struct RawMap<'a, 'de> { - de: &'a mut Deserializer<'de>, - len: usize, -} -impl<'de> MapAccess<'de> for RawMap<'_, 'de> { - type Error = Error; - fn next_key_seed>( - &mut self, - seed: K, - ) -> Result, Error> { - if self.len == 0 { - return Ok(None); - } - self.len -= 1; - seed.deserialize(&mut *self.de).map(Some) - } - fn next_value_seed>(&mut self, seed: V) -> Result { - seed.deserialize(&mut *self.de) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - struct TestStruct { - a: i32, - b: String, - c: Vec, - d: Inner, - active: bool, - } - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - struct Inner { - e: f32, - } - - #[test] - fn pnbt_serde() { - let t = TestStruct { - a: 123456, - b: "hello world".to_string(), - c: vec![1, 2, 3, 4, 5], - d: Inner { - e: std::f32::consts::PI, - }, - active: true, - }; - - let bytes = to_pnbt(&t).unwrap(); - let decoded: TestStruct = from_pnbt(&bytes).unwrap(); - assert_eq!(t, decoded); - } - - #[test] - fn nbt_arrays_pnbt() { - use crate::{nbt_byte_array, nbt_int_array, nbt_long_array}; - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - struct ArrayStruct { - #[serde(serialize_with = "nbt_byte_array")] - b: Vec, - #[serde(serialize_with = "nbt_int_array")] - i: Vec, - #[serde(serialize_with = "nbt_long_array")] - l: Vec, - } - - let t = ArrayStruct { - b: vec![1, 2, 3], - i: vec![100, 200, 300], - l: vec![1000, 2000, 3000], - }; - - let bytes = to_pnbt(&t).unwrap(); - let decoded: ArrayStruct = from_pnbt(&bytes).unwrap(); - assert_eq!(t, decoded); - } - - #[test] - fn pnbt_compound_manual() { - let mut compound = PNbtCompound::new(); - compound.put_i32(123456); - compound.put_string("manual pnbt"); - compound.put_bool(true); - compound.put_f32(std::f32::consts::PI); - - let bytes = compound.into_bytes(); - let mut reader = PNbtCompound::from_bytes(bytes); - - assert_eq!(reader.get_i32().unwrap(), 123456); - assert_eq!(reader.get_string().unwrap(), "manual pnbt"); - assert!(reader.get_bool().unwrap()); - assert!((reader.get_f32().unwrap() - std::f32::consts::PI).abs() < 0.001); - } -} diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index 6998659aa..a2db86cfb 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -13,6 +13,7 @@ use pumpkin_nbt::{compound::NbtCompound, nbt_long_array}; use rustc_hash::FxHashMap; use tokio::sync::Mutex; use tracing::debug; +use uuid::Uuid; use crate::{ chunk::{ @@ -79,8 +80,9 @@ impl ChunkData { chunk_data: &[u8], position: Vector2, ) -> Result { - let chunk_data = pumpkin_nbt::from_pnbt::(chunk_data) - .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; + let chunk_data = + pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data)) + .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; if chunk_data.x_pos != position.x || chunk_data.z_pos != position.y { return Err(ChunkParsingError::ErrorDeserializingChunk(format!( @@ -221,8 +223,9 @@ impl ChunkData { light_correct: is_light_correct, }; - let result = - pumpkin_nbt::to_pnbt(&nbt_ref).map_err(ChunkSerializingError::ErrorSerializingChunk)?; + let mut result = Vec::new(); + pumpkin_nbt::to_bytes_unnamed(&nbt_ref, &mut result) + .map_err(ChunkSerializingError::ErrorSerializingChunk)?; Ok(result.into()) } @@ -271,8 +274,9 @@ impl ChunkEntityData { chunk_data: &[u8], position: Vector2, ) -> Result { - let chunk_entity_data = pumpkin_nbt::from_pnbt::(chunk_data) - .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; + let chunk_entity_data = + pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data)) + .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; if chunk_entity_data.position[0] != position.x || chunk_entity_data.position[1] != position.y @@ -286,16 +290,27 @@ impl ChunkEntityData { ))); } let mut map = FxHashMap::default(); - for mut entity_nbt in chunk_entity_data.entities { - let uuid = { - let _id = entity_nbt.get_string().ok(); - entity_nbt.get_uuid().ok() - }; - entity_nbt.read_pos = 0; - - let Some(uuid) = uuid else { + for entity_nbt in chunk_entity_data.entities { + let uuid = if let Some(uuid) = entity_nbt.get_int_array("UUID") { + if uuid.len() != 4 { + debug!( + "Entity in chunk {},{} has invalid UUID array length {}: {:?}", + position.x, + position.y, + uuid.len(), + entity_nbt + ); + continue; + } + Uuid::from_u128( + (uuid[0] as u128) << 96 + | (uuid[1] as u128) << 64 + | (uuid[2] as u128) << 32 + | (uuid[3] as u128), + ) + } else { debug!( - "Entity in chunk {},{} is missing UUID or ID: {:?}", + "Entity in chunk {},{} is missing UUID: {:?}", position.x, position.y, entity_nbt ); continue; @@ -319,8 +334,9 @@ impl ChunkEntityData { entities: self.data.lock().await.values().cloned().collect(), }; - let result = - pumpkin_nbt::to_pnbt(&nbt).map_err(ChunkSerializingError::ErrorSerializingChunk)?; + let mut result = Vec::new(); + pumpkin_nbt::to_bytes_unnamed(&nbt, &mut result) + .map_err(ChunkSerializingError::ErrorSerializingChunk)?; Ok(result.into()) } } @@ -478,7 +494,6 @@ struct ChunkNbt { light_correct: bool, } -// Used ONLY for saving (to_bytes). Borrows data, zero clones. #[derive(Serialize)] #[serde(rename_all = "PascalCase")] struct ChunkNbtRef<'a> { @@ -503,12 +518,10 @@ struct ChunkNbtRef<'a> { light_correct: bool, } -use pumpkin_nbt::pnbt::PNbtCompound; - -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] struct EntityNbt { data_version: i32, position: [i32; 2], - entities: Vec, + entities: Vec, } diff --git a/pumpkin-world/src/chunk/format/pump.rs b/pumpkin-world/src/chunk/format/pump.rs index 01b1bc53a..3aef3b720 100644 --- a/pumpkin-world/src/chunk/format/pump.rs +++ b/pumpkin-world/src/chunk/format/pump.rs @@ -55,8 +55,9 @@ where } async fn write(&self, backend: &Self::WriteBackend) -> Result<(), std::io::Error> { - let bytes = - pumpkin_nbt::to_pnbt(&self.data).map_err(|e| std::io::Error::other(e.to_string()))?; + let mut bytes = Vec::new(); + pumpkin_nbt::to_bytes_unnamed(&self.data, &mut bytes) + .map_err(|e| std::io::Error::other(e.to_string()))?; let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest); @@ -70,11 +71,12 @@ where std::io::Read::read_to_end(&mut decoder, &mut decompressed) .map_err(|e| ChunkReadingError::IoError(e.kind()))?; - let data: PumpData = pumpkin_nbt::from_pnbt(&decompressed).map_err(|e| { - ChunkReadingError::ParsingError( - crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()), - ) - })?; + let data: PumpData = pumpkin_nbt::from_bytes_unnamed(std::io::Cursor::new(decompressed)) + .map_err(|e| { + ChunkReadingError::ParsingError( + crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()), + ) + })?; Ok(Self { data, diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index bd4478136..3512469cf 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -84,14 +84,12 @@ pub struct ChunkData { pub dirty: AtomicBool, } -use pumpkin_nbt::pnbt::PNbtCompound; - pub struct ChunkEntityData { /// Chunk X pub x: i32, /// Chunk Z pub z: i32, - pub data: Mutex>, + pub data: Mutex>, pub dirty: AtomicBool, } diff --git a/pumpkin-world/src/data/player_data.rs b/pumpkin-world/src/data/player_data.rs index 7d443c1aa..3cfb2d00f 100644 --- a/pumpkin-world/src/data/player_data.rs +++ b/pumpkin-world/src/data/player_data.rs @@ -1,13 +1,14 @@ -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use std::fs::{File, create_dir_all}; -use std::io::{self, Read, Write}; +use std::io; use std::path::PathBuf; use tracing::{debug, error}; use uuid::Uuid; /// Manages the storage and retrieval of player data from disk and memory cache. /// -/// This struct provides functions to load and save player data to/from PNBT files. +/// This struct provides functions to load and save player data to/from NBT files, +/// with a memory cache to handle player disconnections temporarily. pub struct PlayerDataStorage { /// Path to the directory where player data is stored data_path: PathBuf, @@ -62,7 +63,10 @@ impl PlayerDataStorage { self.get_data_path().join(format!("{uuid}.dat")) } - /// Loads player data from PNBT file. + /// Loads player data from NBT file or cache. + /// + /// This function first checks if player data exists in the cache. + /// If not, it attempts to load the data from a .dat file on disk. /// /// # Arguments /// @@ -71,20 +75,20 @@ impl PlayerDataStorage { /// # Returns /// /// A Result containing either the player's NBT data or an error. - pub fn load_player_data(&self, uuid: &Uuid) -> Result<(bool, PNbtCompound), PlayerDataError> { + pub fn load_player_data(&self, uuid: &Uuid) -> Result<(bool, NbtCompound), PlayerDataError> { // If player data saving is disabled, return empty data if !self.is_save_enabled() { - return Ok((false, PNbtCompound::new())); + return Ok((false, NbtCompound::new())); } - // Load from disk + // If not in cache, load from disk let path = self.get_player_data_path(uuid); if !path.exists() { debug!("No player data file found for {uuid}"); - return Ok((false, PNbtCompound::new())); + return Ok((false, NbtCompound::new())); } - let mut file = match File::open(&path) { + let file = match File::open(&path) { Ok(file) => file, Err(e) => { error!("Failed to open player data file for {uuid}: {e}"); @@ -92,14 +96,22 @@ impl PlayerDataStorage { } }; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - - debug!("Loaded player data for {uuid} from disk (PNBT Raw)"); - Ok((true, PNbtCompound::from_bytes(bytes))) + match pumpkin_nbt::nbt_compress::read_gzip_compound_tag(file) { + Ok(nbt) => { + debug!("Loaded player data for {uuid} from disk"); + Ok((true, nbt)) + } + Err(e) => { + error!("Failed to read player data for {uuid}: {e}"); + Err(PlayerDataError::Nbt(e.to_string())) + } + } } - /// Saves player data to PNBT file. + /// Saves player data to NBT file and updates cache. + /// + /// This function saves the player's data to a .dat file on disk and also + /// updates the in-memory cache with the latest data. /// /// # Arguments /// @@ -109,7 +121,7 @@ impl PlayerDataStorage { /// # Returns /// /// A Result indicating success or the error that occurred. - pub fn save_player_data(&self, uuid: &Uuid, data: PNbtCompound) -> Result<(), PlayerDataError> { + pub fn save_player_data(&self, uuid: &Uuid, data: NbtCompound) -> Result<(), PlayerDataError> { // Skip saving if disabled in config if !self.is_save_enabled() { return Ok(()); @@ -125,16 +137,14 @@ impl PlayerDataStorage { return Err(PlayerDataError::Io(e)); } - let bytes = data.into_bytes(); - - // Create the file and write PNBT bytes + // Create the file and write directly with GZip compression match File::create(&path) { - Ok(mut file) => { - if let Err(e) = file.write_all(&bytes) { - error!("Failed to write player data for {uuid}: {e}"); - Err(PlayerDataError::Io(e)) + Ok(file) => { + if let Err(e) = pumpkin_nbt::nbt_compress::write_gzip_compound_tag(data, file) { + error!("Failed to write compressed player data for {uuid}: {e}"); + Err(PlayerDataError::Nbt(e.to_string())) } else { - debug!("Saved player data for {uuid} to disk (PNBT Raw)"); + debug!("Saved player data for {uuid} to disk"); Ok(()) } } diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs index ba2adb312..ba319912d 100644 --- a/pumpkin-world/src/world_info/anvil.rs +++ b/pumpkin-world/src/world_info/anvil.rs @@ -1,6 +1,6 @@ use std::{ fs::File, - io::{Read, Write}, + io::{Cursor, Read}, path::Path, time::{SystemTime, UNIX_EPOCH}, }; @@ -21,7 +21,7 @@ pub const LEVEL_DAT_BACKUP_FILE_NAME: &str = "level.dat_old"; pub struct AnvilLevelInfo; -fn check_file_data_version(raw_pnbt: &[u8]) -> Result<(), WorldInfoError> { +fn check_file_data_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> { // Define a struct that only has the data version. This is necessary because if a user tries to // load a world with different data, they will get a generic "Failed to deserialize level.dat error". // When only checking for the data version, we can determine if we can support the full @@ -37,10 +37,10 @@ fn check_file_data_version(raw_pnbt: &[u8]) -> Result<(), WorldInfoError> { data: LevelData, } - let info: LevelDat = pumpkin_nbt::from_bytes(std::io::Cursor::new(raw_pnbt)).map_err(|e| { - error!("The level.dat file does not have a data version or is not a valid NBT file!"); - WorldInfoError::DeserializationError(e.to_string()) - })?; + let info: LevelDat = pumpkin_nbt::from_bytes(Cursor::new(raw_nbt)) + .map_err(|e|{ + error!("The level.dat file does not have a data version! This means it is either corrupt or very old (read unsupported)"); + WorldInfoError::DeserializationError(e.to_string())})?; let data_version = info.data.data_version; @@ -53,7 +53,7 @@ fn check_file_data_version(raw_pnbt: &[u8]) -> Result<(), WorldInfoError> { } } -fn check_file_level_version(raw_pnbt: &[u8]) -> Result<(), WorldInfoError> { +fn check_file_level_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> { #[derive(Deserialize)] struct LevelData { version: i32, @@ -64,10 +64,10 @@ fn check_file_level_version(raw_pnbt: &[u8]) -> Result<(), WorldInfoError> { data: LevelData, } - let info: LevelDat = pumpkin_nbt::from_bytes(std::io::Cursor::new(raw_pnbt)).map_err(|e| { - error!("The level.dat file does not have a level version or is not a valid NBT file!"); - WorldInfoError::DeserializationError(e.to_string()) - })?; + let info: LevelDat = pumpkin_nbt::from_bytes(Cursor::new(raw_nbt)) + .map_err(|e|{ + error!("The level.dat file does not have a level version! This means it is either corrupt or very old (read unsupported)"); + WorldInfoError::DeserializationError(e.to_string())})?; let level_version = info.data.version; @@ -89,9 +89,11 @@ impl WorldInfoReader for AnvilLevelInfo { check_file_data_version(&buf)?; check_file_level_version(&buf)?; - let info = pumpkin_nbt::from_bytes::(std::io::Cursor::new(&buf)) + let info = pumpkin_nbt::from_bytes::(Cursor::new(buf)) .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; + // TODO: check version + Ok(info.data) } } @@ -114,14 +116,11 @@ impl WorldInfoWriter for AnvilLevelInfo { let path = level_folder.join(LEVEL_DAT_FILE_NAME); let world_info_file = File::create(path)?; - let mut bytes = Vec::new(); - pumpkin_nbt::to_bytes(&level, &mut bytes) - .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; - // write compressed data into file - let mut encoder = GzEncoder::new(world_info_file, Compression::best()); - encoder.write_all(&bytes)?; - encoder.finish()?; + let compression_writer = GzEncoder::new(world_info_file, Compression::best()); + // TODO: Proper error handling + pumpkin_nbt::to_bytes(&level, compression_writer) + .expect("Failed to write level.dat to disk"); Ok(()) } } @@ -136,16 +135,21 @@ pub struct LevelDat { #[cfg(test)] mod test { - use std::{fs, sync::LazyLock}; + use std::{ + fs, + io::{Cursor, Read}, + sync::LazyLock, + }; + use flate2::read::GzDecoder; use pumpkin_data::game_rules::GameRuleRegistry; - use pumpkin_nbt::{from_bytes_unnamed, to_bytes_unnamed}; + use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes}; use pumpkin_util::{Difficulty, world_seed::Seed}; use temp_dir::TempDir; use crate::{ global_path, - world_info::{DataPacks, LevelData, WorldGenSettings, WorldVersion}, + world_info::{DataPacks, LevelData, WorldGenSettings, WorldInfoError, WorldVersion}, }; use super::{AnvilLevelInfo, LEVEL_DAT_FILE_NAME, LevelDat, WorldInfoReader, WorldInfoWriter}; @@ -243,10 +247,13 @@ mod test { #[test] fn deserialize_level_dat() { - let mut bytes = Vec::new(); - to_bytes_unnamed(&*LEVEL_DAT, &mut bytes).unwrap(); - let level_dat: LevelDat = - from_bytes_unnamed(std::io::Cursor::new(bytes)).expect("Failed to decode NBT"); + let raw_compressed_nbt = fs::read("assets/level_1_21_4.dat").unwrap(); + assert!(!raw_compressed_nbt.is_empty()); + + let mut decoder = GzDecoder::new(&raw_compressed_nbt[..]); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + let level_dat: LevelDat = from_bytes(Cursor::new(buf)).expect("Failed to decode from file"); assert_eq!(level_dat, *LEVEL_DAT); } @@ -254,12 +261,12 @@ mod test { #[test] fn serialize_level_dat() { let mut serialized = Vec::new(); - to_bytes_unnamed(&*LEVEL_DAT, &mut serialized).expect("Failed to encode to NBT"); + to_bytes(&*LEVEL_DAT, &mut serialized).expect("Failed to encode to bytes"); assert!(!serialized.is_empty()); - let level_dat_again: LevelDat = from_bytes_unnamed(std::io::Cursor::new(&serialized)) - .expect("Failed to decode from NBT"); + let level_dat_again: LevelDat = + from_bytes(Cursor::new(serialized)).expect("Failed to decode from bytes"); assert_eq!(level_dat_again, *LEVEL_DAT); } @@ -276,6 +283,10 @@ mod test { .unwrap(); let result = AnvilLevelInfo.read_world_info(temp_dir.path()); - assert!(result.is_err()); + match result { + Ok(_) => panic!("This should fail!"), + Err(WorldInfoError::UnsupportedDataVersion(_)) => {} + Err(_) => panic!("Wrong error!"), + } } } diff --git a/pumpkin/src/command/commands/data.rs b/pumpkin/src/command/commands/data.rs index 5beecd68d..7b6b3bf48 100644 --- a/pumpkin/src/command/commands/data.rs +++ b/pumpkin/src/command/commands/data.rs @@ -9,7 +9,7 @@ use crate::command::{ use crate::entity::NBTStorage; use CommandError::InvalidConsumption; use pumpkin_data::translation; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use pumpkin_util::text::TextComponent; use pumpkin_util::text::color::NamedColor; @@ -43,7 +43,6 @@ impl CommandExecutor for GetEntityDataExecutor { } #[expect(clippy::too_many_lines)] -#[allow(dead_code)] pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result { let folded = TextComponent::text("<...>").color_named(NamedColor::Gray); match tag { @@ -219,18 +218,19 @@ pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result Result { - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); storage.write_nbt(&mut nbt).await; + let tag = NbtTag::Compound(nbt); - // PNBT is positional and doesn't directly map to SNBT. - // For now, we'll just show the length of the data. - let display = TextComponent::text(format!("PNBT Data ({} bytes)", nbt.data.len())); - + let result = get_i32_result(&tag)?; + let display = snbt_colorful_display(&tag, 0) + .map_err(|string| CommandError::CommandFailed(TextComponent::text(string)))?; sender .send_message(TextComponent::translate_cross( translation::java::COMMANDS_DATA_ENTITY_QUERY, @@ -239,10 +239,9 @@ async fn display_data( )) .await; - Ok(1) + Ok(result) } -#[allow(dead_code)] fn get_i32_result(tag: &NbtTag) -> Result { match tag { NbtTag::End => Err(CommandError::CommandFailed(TextComponent::translate_cross( diff --git a/pumpkin/src/data/player_server.rs b/pumpkin/src/data/player_server.rs index 0b8d29fb9..6caeaa98c 100644 --- a/pumpkin/src/data/player_server.rs +++ b/pumpkin/src/data/player_server.rs @@ -4,7 +4,7 @@ use crate::{ }; use crossbeam::atomic::AtomicCell; use pumpkin_inventory::screen_handler::ScreenHandler; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_world::data::player_data::{PlayerDataError, PlayerDataStorage}; use std::sync::Arc; use std::{ @@ -53,7 +53,7 @@ impl ServerPlayerData { .await; player.on_handled_screen_closed().await; - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); player.write_nbt(&mut nbt).await; let storage = self.storage.clone(); @@ -82,7 +82,7 @@ impl ServerPlayerData { // Save all online players periodically across all worlds for world in server.worlds.load().iter() { for player in world.players.load().iter() { - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); player.write_nbt(&mut nbt).await; let storage = self.storage.clone(); @@ -141,7 +141,7 @@ impl ServerPlayerData { pub async fn load_data( &self, uuid: &uuid::Uuid, - ) -> Result, PlayerDataError> { + ) -> Result, PlayerDataError> { let storage = self.storage.clone(); let uuid = *uuid; let result = tokio::task::spawn_blocking(move || storage.load_player_data(&uuid)) @@ -190,7 +190,7 @@ impl ServerPlayerData { } let uuid = player.gameprofile.id; - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); player.write_nbt(&mut nbt).await; let storage = self.storage.clone(); @@ -205,7 +205,7 @@ impl ServerPlayerData { #[cfg(test)] mod test { use crate::data::player_server::ServerPlayerData; - use pumpkin_nbt::pnbt::PNbtCompound; + use pumpkin_nbt::compound::NbtCompound; use pumpkin_world::data::player_data::PlayerDataStorage; use std::time::Duration; use std::time::Instant; @@ -247,19 +247,19 @@ mod test { let uuid = Uuid::new_v4(); // Create test data - let mut nbt = PNbtCompound::new(); - nbt.put_string("TestValue"); - nbt.put_i32(42); + let mut nbt = NbtCompound::new(); + nbt.put_string("TestKey", "TestValue".to_string()); + nbt.put_int("TestInt", 42); // Save the data storage.save_player_data(&uuid, nbt).unwrap(); // Load the data - let (load_success, mut loaded_nbt) = storage.load_player_data(&uuid).unwrap(); + let (load_success, loaded_nbt) = storage.load_player_data(&uuid).unwrap(); assert!(load_success); - assert_eq!(loaded_nbt.get_string().unwrap(), "TestValue"); - assert_eq!(loaded_nbt.get_i32().unwrap(), 42); + assert_eq!(loaded_nbt.get_string("TestKey").unwrap(), "TestValue"); + assert_eq!(loaded_nbt.get_int("TestInt").unwrap(), 42); } #[tokio::test] @@ -275,7 +275,7 @@ mod test { let (load_success, empty_nbt) = storage.load_player_data(&uuid).unwrap(); assert!(!load_success); - assert_eq!(empty_nbt.as_bytes().len(), 0); + assert_eq!(empty_nbt.child_tags.len(), 0); } #[tokio::test] @@ -286,8 +286,8 @@ mod test { let storage = PlayerDataStorage::new(path, false); let uuid = Uuid::new_v4(); - let mut nbt = PNbtCompound::new(); - nbt.put_string("TestValue"); + let mut nbt = NbtCompound::new(); + nbt.put_string("TestKey", "TestValue".to_string()); // Save should succeed but do nothing let save_result = storage.save_player_data(&uuid, nbt); @@ -296,7 +296,7 @@ mod test { // Load should return empty data let (load_success, empty_nbt) = storage.load_player_data(&uuid).unwrap(); assert!(!load_success); - assert_eq!(empty_nbt.as_bytes().len(), 0); + assert_eq!(empty_nbt.child_tags.len(), 0); } #[tokio::test] @@ -322,9 +322,9 @@ mod test { let storage = PlayerDataStorage::new(path, true); // Create and save player data - let mut nbt = PNbtCompound::new(); - nbt.put_string("TestPlayer"); - nbt.put_i32(42); + let mut nbt = NbtCompound::new(); + nbt.put_string("name", "TestPlayer".to_string()); + nbt.put_int("level", 42); storage.save_player_data(&uuid, nbt).unwrap(); // Verify the file exists @@ -332,9 +332,9 @@ mod test { assert!(player_data_path.exists()); // Load it again and verify content - let (success, mut loaded_data) = storage.load_player_data(&uuid).unwrap(); + let (success, loaded_data) = storage.load_player_data(&uuid).unwrap(); assert!(success); - assert_eq!(loaded_data.get_string().unwrap(), "TestPlayer"); - assert_eq!(loaded_data.get_i32().unwrap(), 42); + assert_eq!(loaded_data.get_string("name").unwrap(), "TestPlayer"); + assert_eq!(loaded_data.get_int("level").unwrap(), 42); } } diff --git a/pumpkin/src/entity/decoration/armor_stand.rs b/pumpkin/src/entity/decoration/armor_stand.rs index 41ea264f6..f78e1d0f5 100644 --- a/pumpkin/src/entity/decoration/armor_stand.rs +++ b/pumpkin/src/entity/decoration/armor_stand.rs @@ -236,116 +236,74 @@ impl ArmorStandEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for ArmorStandEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.living_entity.write_nbt(nbt).await; let disabled_slots = self.disabled_slots.load(Ordering::Relaxed); // ... - nbt.put_bool(self.is_invisible()); - nbt.put_bool(self.is_small()); - nbt.put_bool(self.should_show_arms()); - nbt.put_int(disabled_slots); - nbt.put_bool(!self.should_show_base_plate()); - nbt.put_bool(self.is_marker()); + nbt.put_bool("Invisible", self.is_invisible()); + nbt.put_bool("Small", self.is_small()); + nbt.put_bool("ShowArms", self.should_show_arms()); + nbt.put_int("DisabledSlots", disabled_slots); + nbt.put_bool("NoBasePlate", !self.should_show_base_plate()); + if self.is_marker() { + nbt.put_bool("Marker", true); + } - let pose = self.pack_rotation(); - nbt.put_f32(pose.head.pitch); - nbt.put_f32(pose.head.yaw); - nbt.put_f32(pose.head.roll); - nbt.put_f32(pose.body.pitch); - nbt.put_f32(pose.body.yaw); - nbt.put_f32(pose.body.roll); - nbt.put_f32(pose.left_arm.pitch); - nbt.put_f32(pose.left_arm.yaw); - nbt.put_f32(pose.left_arm.roll); - nbt.put_f32(pose.right_arm.pitch); - nbt.put_f32(pose.right_arm.yaw); - nbt.put_f32(pose.right_arm.roll); - nbt.put_f32(pose.left_leg.pitch); - nbt.put_f32(pose.left_leg.yaw); - nbt.put_f32(pose.left_leg.roll); - nbt.put_f32(pose.right_leg.pitch); - nbt.put_f32(pose.right_leg.yaw); - nbt.put_f32(pose.right_leg.roll); + nbt.put("Pose", self.pack_rotation()); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.living_entity.read_nbt_non_mut(nbt).await; let mut flags = 0u8; // ... - let invisible = nbt.get_bool().unwrap_or(false); - if invisible { + if let Some(invisible) = nbt.get_bool("Invisible") + && invisible + { self.get_entity().set_invisible(invisible).await; } - if nbt.get_bool().unwrap_or(false) { + if let Some(small) = nbt.get_bool("Small") + && small + { flags |= ArmorStandFlags::Small as u8; } - if nbt.get_bool().unwrap_or(false) { + if let Some(show_arms) = nbt.get_bool("ShowArms") + && show_arms + { flags |= ArmorStandFlags::ShowArms as u8; } - let disabled_slots = nbt.get_int().unwrap_or(0); - self.disabled_slots.store(disabled_slots, Ordering::Relaxed); + if let Some(disabled_slots) = nbt.get_int("DisabledSlots") { + self.disabled_slots.store(disabled_slots, Ordering::Relaxed); + } - let no_base_plate = nbt.get_bool().unwrap_or(false); - if !no_base_plate { + if let Some(no_base_plate) = nbt.get_bool("NoBasePlate") { + if !no_base_plate { + flags |= ArmorStandFlags::HideBasePlate as u8; + } + } else { flags |= ArmorStandFlags::HideBasePlate as u8; } - if nbt.get_bool().unwrap_or(false) { + if let Some(marker) = nbt.get_bool("Marker") + && marker + { flags |= ArmorStandFlags::Marker as u8; } self.armor_stand_flags.store(flags, Ordering::Relaxed); - let head = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - let body = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - let left_arm = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - let right_arm = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - let left_leg = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - let right_leg = EulerAngle::new( - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - nbt.get_f32().unwrap_or(0.0), - ); - - self.unpack_rotation(&PackedRotation { - head, - body, - left_arm, - right_arm, - left_leg, - right_leg, - }); + if let Some(pose_tag) = nbt.get("Pose") { + let packed: PackedRotation = pose_tag.clone().into(); + self.unpack_rotation(&packed); + } }) } } diff --git a/pumpkin/src/entity/decoration/painting.rs b/pumpkin/src/entity/decoration/painting.rs index 7e3949ada..d5642baf2 100644 --- a/pumpkin/src/entity/decoration/painting.rs +++ b/pumpkin/src/entity/decoration/painting.rs @@ -5,6 +5,7 @@ use crate::entity::{ Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, living::LivingEntity, }; use pumpkin_data::damage::DamageType; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::vector3::Vector3; pub struct PaintingEntity { @@ -17,20 +18,18 @@ impl PaintingEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for PaintingEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.entity.write_nbt(nbt).await; - nbt.put_byte(self.entity.data.load(Ordering::Relaxed) as i8); + nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.entity.read_nbt_non_mut(nbt).await; - let facing = nbt.get_byte().unwrap_or(3); + let facing = nbt.get_byte("facing").unwrap_or(3); self.entity.data.store(facing as i32, Ordering::Relaxed); }) } diff --git a/pumpkin/src/entity/effect/mod.rs b/pumpkin/src/entity/effect/mod.rs index c28fe1c47..c7a8f0c70 100644 --- a/pumpkin/src/entity/effect/mod.rs +++ b/pumpkin/src/entity/effect/mod.rs @@ -1,38 +1,52 @@ use crate::entity::{NBTInitFuture, NBTStorage, NBTStorageInit, NbtFuture}; use pumpkin_data::effect::StatusEffect; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; use tracing::warn; impl NBTStorage for pumpkin_data::potion::Effect { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - nbt.put_string(self.effect_type.minecraft_name); - nbt.put_u8(self.amplifier); - nbt.put_int(self.duration); - nbt.put_bool(self.ambient); - nbt.put_bool(self.show_particles); - nbt.put_bool(self.show_icon); + nbt.put("id", self.effect_type.minecraft_name); + if self.amplifier > 0 { + nbt.put("amplifier", NbtTag::Int(i32::from(self.amplifier))); + } + nbt.put("duration", NbtTag::Int(self.duration)); + if self.ambient { + nbt.put("ambient", NbtTag::Byte(1)); + } + if !self.show_particles { + nbt.put("show_particles", NbtTag::Byte(0)); + } + let show_icon: i8 = i8::from(self.show_icon); + nbt.put("show_icon", NbtTag::Byte(show_icon)); }) } } impl NBTStorageInit for pumpkin_data::potion::Effect { - fn create_from_nbt<'a>(nbt: &'a mut PNbtCompound) -> NBTInitFuture<'a, Self> + fn create_from_nbt<'a>(nbt: &'a mut NbtCompound) -> NBTInitFuture<'a, Self> where Self: 'a, { Box::pin(async move { - let effect_id = nbt.get_string().ok()?; - let effect_type = StatusEffect::from_minecraft_name(&effect_id).or_else(|| { + let Some(effect_id) = nbt.get_string("id") else { + warn!("Unable to read effect. Effect id is not present"); + return None; + }; + let Some(effect_type) = StatusEffect::from_minecraft_name(effect_id) else { warn!("Unable to read effect. Unknown effect type: {effect_id}"); - None - })?; - let amplifier = nbt.get_u8().unwrap_or(0); - let duration = nbt.get_int().unwrap_or(0); - let ambient = nbt.get_bool().unwrap_or(false); - let show_particles = nbt.get_bool().unwrap_or(true); - let show_icon = nbt.get_bool().unwrap_or(true); - + return None; + }; + let Some(show_icon) = nbt.get_byte("show_icon") else { + warn!("Unable to read effect. Show icon is not present"); + return None; + }; + let amplifier = nbt.get_int("amplifier").unwrap_or(0) as u8; + let duration = nbt.get_int("duration").unwrap_or(0); + let ambient = nbt.get_byte("ambient").unwrap_or(0) == 1; + let show_particles = nbt.get_byte("show_particles").unwrap_or(1) == 1; + let show_icon = show_icon == 1; Some(Self { effect_type, duration, diff --git a/pumpkin/src/entity/hunger.rs b/pumpkin/src/entity/hunger.rs index ffafe54af..12d15d829 100644 --- a/pumpkin/src/entity/hunger.rs +++ b/pumpkin/src/entity/hunger.rs @@ -4,6 +4,7 @@ use super::{EntityBase, NBTStorage, NBTStorageInit, player::Player}; use crate::entity::NbtFuture; use crossbeam::atomic::AtomicCell; use pumpkin_data::damage::DamageType; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::Difficulty; const MAX_FOOD: u8 = 20; @@ -174,24 +175,26 @@ impl HungerManager { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for HungerManager { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - nbt.put_int(self.level.load().into()); - nbt.put_float(self.saturation.load()); - nbt.put_float(self.exhaustion.load()); - nbt.put_int(self.tick_timer.load() as i32); + nbt.put_int("foodLevel", self.level.load().into()); + nbt.put_float("foodSaturationLevel", self.saturation.load()); + nbt.put_float("foodExhaustionLevel", self.exhaustion.load()); + nbt.put_int("foodTickTimer", self.tick_timer.load() as i32); }) } - fn read_nbt<'a>(&'a mut self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt<'a>(&'a mut self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.level.store(nbt.get_int().unwrap_or(20) as u8); - self.saturation.store(nbt.get_float().unwrap_or(5.0)); - self.exhaustion.store(nbt.get_float().unwrap_or(0.0)); - self.tick_timer.store(nbt.get_int().unwrap_or(0) as u32); + self.level + .store(nbt.get_int("foodLevel").unwrap_or(20) as u8); + self.saturation + .store(nbt.get_float("foodSaturationLevel").unwrap_or(5.0)); + self.exhaustion + .store(nbt.get_float("foodExhaustionLevel").unwrap_or(0.0)); + self.tick_timer + .store(nbt.get_int("foodTickTimer").unwrap_or(0) as u32); }) } } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index a3e9fc7f5..d829561b8 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -44,7 +44,8 @@ use pumpkin_data::sound::SoundCategory; use pumpkin_data::{Block, translation}; use pumpkin_data::{damage::DamageType, sound::Sound}; use pumpkin_inventory::entity_equipment::EntityEquipment; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::{ Animation, CEntityAnimation, CHurtAnimation, CSetPlayerInventory, CTakeItemEntity, @@ -1780,10 +1781,10 @@ impl LivingEntity { } impl NBTStorage for LivingEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.entity.write_nbt(nbt).await; - nbt.put_float(self.health.load()); + nbt.put("Health", NbtTag::Float(self.health.load())); // Avoid persisting a lethal fall distance when the entity is dead to prevent death loops let fall_distance = if self.dead.load(Relaxed) { 0.0 @@ -1791,13 +1792,19 @@ impl NBTStorage for LivingEntity { self.fall_distance.load() }; // Persist current absorption amount - nbt.put_float(self.absorption.load()); - nbt.put_float(fall_distance); + nbt.put("AbsorptionAmount", NbtTag::Float(self.absorption.load())); + nbt.put("fall_distance", NbtTag::Float(fall_distance)); { let effects = self.active_effects.lock().await; - nbt.put_u32(effects.len() as u32); - for effect in effects.values() { - effect.write_nbt(nbt).await; + if !effects.is_empty() { + // Iterate effects and create Box<[NbtTag]> + let mut effects_list = Vec::with_capacity(effects.len()); + for effect in effects.values() { + let mut effect_nbt = pumpkin_nbt::compound::NbtCompound::new(); + effect.write_nbt(&mut effect_nbt).await; + effects_list.push(NbtTag::Compound(effect_nbt)); + } + nbt.put("active_effects", NbtTag::List(effects_list)); } } //TODO: write equipment @@ -1805,20 +1812,20 @@ impl NBTStorage for LivingEntity { }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.entity.read_nbt_non_mut(nbt).await; - self.health.store(nbt.get_float().unwrap_or(0.0)); + self.health.store(nbt.get_float("Health").unwrap_or(0.0)); // Clamp any persisted absorption to the entity's configured max - let raw_abs = nbt.get_float().unwrap_or(0.0); + let raw_abs = nbt.get_float("AbsorptionAmount").unwrap_or(0.0); let max_abs = self.get_attribute_value(&Attributes::MAX_ABSORPTION) as f32; let clamped_abs = raw_abs.max(0.0).min(max_abs); self.absorption.store(clamped_abs); // Load fall distance, but if this entity is currently marked dead ensure we don't restore // a lethal fall distance that would immediately re-kill on spawn. - let fd = nbt.get_float().unwrap_or(0.0); + let fd = nbt.get_float("fall_distance").unwrap_or(0.0); if self.dead.load(Relaxed) { self.fall_distance.store(0.0); } else { @@ -1826,16 +1833,20 @@ impl NBTStorage for LivingEntity { } { let mut active_effects = self.active_effects.lock().await; - let effects_len = nbt.get_u32().unwrap_or(0); - for _ in 0..effects_len { - let effect = Effect::create_from_nbt(nbt).await; - if effect.is_none() { - warn!("Unable to read effect from nbt"); - continue; + let nbt_effects = nbt.get_list("active_effects"); + if let Some(nbt_effects) = nbt_effects { + for effect in nbt_effects { + if let NbtTag::Compound(effect_nbt) = effect { + let effect = Effect::create_from_nbt(&mut effect_nbt.clone()).await; + if effect.is_none() { + warn!("Unable to read effect from nbt"); + continue; + } + let mut effect = effect.unwrap(); + effect.blend = true; // TODO: change, is taken from effect give command + active_effects.insert(effect.effect_type, effect); + } } - let mut effect = effect.unwrap(); - effect.blend = true; // TODO: change, is taken from effect give command - active_effects.insert(effect.effect_type, effect); } } }) diff --git a/pumpkin/src/entity/mob/bat.rs b/pumpkin/src/entity/mob/bat.rs index 6341c9bcd..9faa73a56 100644 --- a/pumpkin/src/entity/mob/bat.rs +++ b/pumpkin/src/entity/mob/bat.rs @@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering::Relaxed}; use pumpkin_data::damage::DamageType; use pumpkin_data::sound::Sound; use pumpkin_data::tag::{self, Taggable}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::chunk::ChunkHeightmapType; @@ -92,21 +93,19 @@ impl BatEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for BatEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.mob_entity.living_entity.write_nbt(nbt).await; let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 }; - nbt.put_byte(flags as i8); + nbt.put_byte("BatFlags", flags as i8); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - let flags = nbt.get_byte().unwrap_or(0) as u8; + let flags = nbt.get_byte("BatFlags").unwrap_or(0) as u8; let roosting = (flags & ROOSTING_FLAG) != 0; self.set_roosting(roosting).await; }) diff --git a/pumpkin/src/entity/mob/creeper.rs b/pumpkin/src/entity/mob/creeper.rs index 17ec20566..30847e224 100644 --- a/pumpkin/src/entity/mob/creeper.rs +++ b/pumpkin/src/entity/mob/creeper.rs @@ -11,6 +11,7 @@ use pumpkin_data::{ sound::{Sound, SoundCategory}, tracked_data::TrackedData, }; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata}; use crate::entity::{ @@ -116,30 +117,36 @@ impl CreeperEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for CreeperEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.write_nbt(nbt).await; - nbt.put_bool(self.charged.load(Ordering::Relaxed)); - nbt.put_short(self.fuse_time.load(Ordering::Relaxed) as i16); - nbt.put_byte(self.explosion_radius.load(Ordering::Relaxed) as i8); - nbt.put_bool(self.ignited.load(Ordering::Relaxed)); + nbt.put_bool("powered", self.charged.load(Ordering::Relaxed)); + nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16); + nbt.put_byte( + "ExplosionRadius", + self.explosion_radius.load(Ordering::Relaxed) as i8, + ); + nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed)); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - self.charged - .store(nbt.get_bool().unwrap_or(false), Ordering::Relaxed); - self.fuse_time - .store(i32::from(nbt.get_short().unwrap_or(30)), Ordering::Relaxed); - self.explosion_radius - .store(i32::from(nbt.get_byte().unwrap_or(3)), Ordering::Relaxed); - self.ignited - .store(nbt.get_bool().unwrap_or(false), Ordering::Relaxed); + if let Some(powered) = nbt.get_bool("powered") { + self.charged.store(powered, Ordering::Relaxed); + } + if let Some(fuse) = nbt.get_short("Fuse") { + self.fuse_time.store(i32::from(fuse), Ordering::Relaxed); + } + if let Some(radius) = nbt.get_byte("ExplosionRadius") { + self.explosion_radius + .store(i32::from(radius), Ordering::Relaxed); + } + if let Some(ignited) = nbt.get_bool("ignited") { + self.ignited.store(ignited, Ordering::Relaxed); + } }) } } diff --git a/pumpkin/src/entity/mob/enderman.rs b/pumpkin/src/entity/mob/enderman.rs index 556d98545..0144d17dd 100644 --- a/pumpkin/src/entity/mob/enderman.rs +++ b/pumpkin/src/entity/mob/enderman.rs @@ -22,6 +22,7 @@ use pumpkin_data::{ tag::Taggable, tracked_data::TrackedData, }; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::{ codec::var_int::VarInt, java::client::play::{CEntityPositionSync, Metadata}, @@ -396,26 +397,20 @@ impl EndermanEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for EndermanEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.write_nbt(nbt).await; if let Some(block_state) = self.carried_block.load() { - nbt.put_bool(true); - nbt.put_int(block_state as i32); - } else { - nbt.put_bool(false); + nbt.put_int("carriedBlockState", block_state as i32); } }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - if nbt.get_bool().unwrap_or(false) { - let block_state = nbt.get_int().unwrap_or(0); + if let Some(block_state) = nbt.get_int("carriedBlockState") { self.set_carried_block(Some(block_state as u16)).await; } }) diff --git a/pumpkin/src/entity/mob/shulker.rs b/pumpkin/src/entity/mob/shulker.rs index 2eddff1f7..267748011 100644 --- a/pumpkin/src/entity/mob/shulker.rs +++ b/pumpkin/src/entity/mob/shulker.rs @@ -8,7 +8,7 @@ use pumpkin_data::entity::EntityType; use pumpkin_data::meta_data_type::MetaDataType; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::tracked_data::TrackedData; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::{CEntityPositionSync, Metadata}; use pumpkin_util::math::position::BlockPos; @@ -320,25 +320,25 @@ impl ShulkerEntity { } impl NBTStorage for ShulkerEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.mob_entity.living_entity.write_nbt(nbt).await; - nbt.put_byte(self.attach_face.load(Ordering::Relaxed) as i8); - nbt.put_byte(self.peek_amount.load(Ordering::Relaxed) as i8); - nbt.put_byte(self.color.load(Ordering::Relaxed) as i8); + nbt.put_byte("AttachFace", self.attach_face.load(Ordering::Relaxed) as i8); + nbt.put_byte("PeekAmount", self.peek_amount.load(Ordering::Relaxed) as i8); + nbt.put_byte("Color", self.color.load(Ordering::Relaxed) as i8); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - if let Ok(face) = nbt.get_byte() { + if let Some(face) = nbt.get_byte("AttachFace") { self.attach_face.store(face as u8, Ordering::Relaxed); } - if let Ok(peek) = nbt.get_byte() { + if let Some(peek) = nbt.get_byte("PeekAmount") { self.peek_amount.store(peek as u8, Ordering::Relaxed); } - if let Ok(color) = nbt.get_byte() { + if let Some(color) = nbt.get_byte("Color") { self.color.store(color as u8, Ordering::Relaxed); } }) diff --git a/pumpkin/src/entity/mob/skeleton/mod.rs b/pumpkin/src/entity/mob/skeleton/mod.rs index 6e85290c6..2d115beca 100644 --- a/pumpkin/src/entity/mob/skeleton/mod.rs +++ b/pumpkin/src/entity/mob/skeleton/mod.rs @@ -11,7 +11,7 @@ use crate::entity::{ }, mob::{Mob, MobEntity}, }; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; pub mod bogged; pub mod parched; @@ -58,11 +58,11 @@ impl SkeletonEntityBase { } impl NBTStorage for SkeletonEntityBase { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.write_nbt(nbt) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.read_nbt_non_mut(nbt) } } diff --git a/pumpkin/src/entity/mob/slime.rs b/pumpkin/src/entity/mob/slime.rs index bfac3ae38..cab155fc9 100644 --- a/pumpkin/src/entity/mob/slime.rs +++ b/pumpkin/src/entity/mob/slime.rs @@ -1,7 +1,9 @@ +use std::sync::atomic::Ordering; use std::sync::{Arc, Weak}; use pumpkin_data::entity::EntityType; use pumpkin_data::sound::Sound; +use pumpkin_nbt::compound::NbtCompound; use crate::entity::{ Entity, NBTStorage, NbtFuture, @@ -58,18 +60,29 @@ impl SlimeEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for SlimeEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.entity.living_entity.write_nbt(nbt).await; + nbt.put_int( + "Size", + self.entity + .living_entity + .entity + .data + .load(Ordering::Relaxed), + ); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.entity.living_entity.read_nbt_non_mut(nbt).await; + self.entity + .living_entity + .entity + .data + .store(nbt.get_int("Size").unwrap_or(0), Ordering::Relaxed); }) } } diff --git a/pumpkin/src/entity/mob/zombie/mod.rs b/pumpkin/src/entity/mob/zombie/mod.rs index 7ac73c20c..c185b2c61 100644 --- a/pumpkin/src/entity/mob/zombie/mod.rs +++ b/pumpkin/src/entity/mob/zombie/mod.rs @@ -10,7 +10,7 @@ use crate::entity::{ ai::goal::{active_target::ActiveTargetGoal, look_at_entity::LookAtEntityGoal}, }; use pumpkin_data::entity::EntityType; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use std::sync::{Arc, Weak}; pub mod drowned; @@ -71,11 +71,11 @@ impl ZombieEntityBase { } impl NBTStorage for ZombieEntityBase { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.write_nbt(nbt) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.read_nbt_non_mut(nbt) } } diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 71a3a040f..105783ce1 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -31,6 +31,7 @@ use pumpkin_data::{ entity::{EntityPose, EntityType}, sound::{Sound, SoundCategory}, }; +use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot}; use pumpkin_protocol::{ PositionFlag, @@ -2764,55 +2765,74 @@ impl Entity { } impl NBTStorage for Entity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { let position = self.pos.load(); - nbt.put_string(self.entity_type.resource_name); - nbt.put_uuid(&self.entity_uuid); - - // Pos - nbt.put_f64(position.x); - nbt.put_f64(position.y); - nbt.put_f64(position.z); - - // Motion + nbt.put_string( + "id", + format!("minecraft:{}", self.entity_type.resource_name), + ); + let uuid = self.entity_uuid.as_u128(); + nbt.put( + "UUID", + NbtTag::IntArray(vec![ + (uuid >> 96) as i32, + ((uuid >> 64) & 0xFFFF_FFFF) as i32, + ((uuid >> 32) & 0xFFFF_FFFF) as i32, + (uuid & 0xFFFF_FFFF) as i32, + ]), + ); + nbt.put( + "Pos", + NbtTag::List(vec![ + position.x.into(), + position.y.into(), + position.z.into(), + ]), + ); let velocity = self.velocity.load(); - nbt.put_f64(velocity.x); - nbt.put_f64(velocity.y); - nbt.put_f64(velocity.z); - - // Rotation - nbt.put_f32(self.yaw.load()); - nbt.put_f32(self.pitch.load()); - - nbt.put_short(self.fire_ticks.load(Relaxed) as i16); - nbt.put_bool(self.on_ground.load(Relaxed)); - nbt.put_bool(self.invulnerable.load(Relaxed)); - nbt.put_int(self.portal_cooldown.load(Relaxed) as i32); - nbt.put_bool(self.has_visual_fire.load(Relaxed)); + nbt.put( + "Motion", + NbtTag::List(vec![ + velocity.x.into(), + velocity.y.into(), + velocity.z.into(), + ]), + ); + nbt.put( + "Rotation", + NbtTag::List(vec![self.yaw.load().into(), self.pitch.load().into()]), + ); + nbt.put_short("Fire", self.fire_ticks.load(Relaxed) as i16); + nbt.put_bool("OnGround", self.on_ground.load(Relaxed)); + nbt.put_bool("Invulnerable", self.invulnerable.load(Relaxed)); + nbt.put_int("PortalCooldown", self.portal_cooldown.load(Relaxed) as i32); + if self.has_visual_fire.load(Relaxed) { + nbt.put_bool("HasVisualFire", true); + } // todo more... }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - let _id = nbt.get_string().unwrap(); - let _uuid = nbt.get_uuid().unwrap(); - - let x = nbt.get_f64().unwrap_or(0.0); - let y = nbt.get_f64().unwrap_or(0.0); - let z = nbt.get_f64().unwrap_or(0.0); + let position = nbt.get_list("Pos").unwrap(); + let x = position[0].extract_double().unwrap_or(0.0); + let y = position[1].extract_double().unwrap_or(0.0); + let z = position[2].extract_double().unwrap_or(0.0); let pos = Vector3::new(x, y, z); self.set_pos(pos); self.last_sent_pos.store(pos); self.first_loaded_chunk_position.store(Some(pos.to_i32())); - let vx = nbt.get_f64().unwrap_or(0.0); - let vy = nbt.get_f64().unwrap_or(0.0); - let vz = nbt.get_f64().unwrap_or(0.0); - self.velocity.store(Vector3::new(vx, vy, vz)); - let yaw = nbt.get_f32().unwrap_or(0.0); - let pitch = nbt.get_f32().unwrap_or(0.0); + let velocity = nbt.get_list("Motion").unwrap(); + let x = velocity[0].extract_double().unwrap_or(0.0); + let y = velocity[1].extract_double().unwrap_or(0.0); + let z = velocity[2].extract_double().unwrap_or(0.0); + self.velocity.store(Vector3::new(x, y, z)); + let rotation = nbt.get_list("Rotation").unwrap(); + let yaw = rotation[0].extract_float().unwrap_or(0.0); + let pitch = rotation[1].extract_float().unwrap_or(0.0); self.set_rotation(yaw, pitch); let yaw_byte = (yaw * 256.0 / 360.0).rem_euclid(256.0) as u8; let pitch_byte = (pitch * 256.0 / 360.0).rem_euclid(256.0) as u8; @@ -2821,15 +2841,15 @@ impl NBTStorage for Entity { self.head_yaw.store(yaw); self.last_sent_head_yaw.store(yaw_byte, Relaxed); self.fire_ticks - .store(i32::from(nbt.get_short().unwrap_or(0)), Relaxed); + .store(i32::from(nbt.get_short("Fire").unwrap_or(0)), Relaxed); self.on_ground - .store(nbt.get_bool().unwrap_or(false), Relaxed); + .store(nbt.get_bool("OnGround").unwrap_or(false), Relaxed); self.invulnerable - .store(nbt.get_bool().unwrap_or(false), Relaxed); + .store(nbt.get_bool("Invulnerable").unwrap_or(false), Relaxed); self.portal_cooldown - .store(nbt.get_int().unwrap_or(0) as u32, Relaxed); + .store(nbt.get_int("PortalCooldown").unwrap_or(0) as u32, Relaxed); self.has_visual_fire - .store(nbt.get_bool().unwrap_or(false), Relaxed); + .store(nbt.get_bool("HasVisualFire").unwrap_or(false), Relaxed); // todo more... }) } @@ -2924,22 +2944,20 @@ impl EntityBase for Entity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - pub type NbtFuture<'a, T> = Pin + Send + 'a>>; pub trait NBTStorage: Send + Sync { - fn write_nbt<'a>(&'a self, _nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, _nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async {}) } - fn read_nbt<'a>(&'a mut self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt<'a>(&'a mut self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { self.read_nbt_non_mut(nbt).await; }) } - fn read_nbt_non_mut<'a>(&'a self, _nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, _nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async {}) } } @@ -2947,7 +2965,7 @@ pub trait NBTStorage: Send + Sync { pub type NBTInitFuture<'a, T> = Pin> + Send + 'a>>; pub trait NBTStorageInit: Send + Sync + Sized { - fn create_from_nbt<'a>(_nbt: &'a mut PNbtCompound) -> NBTInitFuture<'a, Self> + fn create_from_nbt<'a>(_nbt: &'a mut NbtCompound) -> NBTInitFuture<'a, Self> where Self: 'a, { diff --git a/pumpkin/src/entity/passive/chicken.rs b/pumpkin/src/entity/passive/chicken.rs index 6c342117e..95c544f2e 100644 --- a/pumpkin/src/entity/passive/chicken.rs +++ b/pumpkin/src/entity/passive/chicken.rs @@ -20,7 +20,7 @@ use crate::entity::{ mob::{Mob, MobEntity}, player::Player, }; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; const TEMPT_ITEMS: &[&Item] = &[ &Item::WHEAT_SEEDS, @@ -74,18 +74,18 @@ impl ChickenEntity { } impl NBTStorage for ChickenEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.write_nbt(nbt).await; - nbt.put_int(self.egg_lay_time.load(Ordering::Relaxed)); + nbt.put_int("EggLayTime", self.egg_lay_time.load(Ordering::Relaxed)); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; self.egg_lay_time - .store(nbt.get_int().unwrap_or(6000), Ordering::Relaxed); + .store(nbt.get_int("EggLayTime").unwrap_or(6000), Ordering::Relaxed); }) } } diff --git a/pumpkin/src/entity/passive/cow.rs b/pumpkin/src/entity/passive/cow.rs index 017e94ff5..5b8a6a2b2 100644 --- a/pumpkin/src/entity/passive/cow.rs +++ b/pumpkin/src/entity/passive/cow.rs @@ -16,7 +16,7 @@ use crate::entity::{ mob::{Mob, MobEntity}, player::Player, }; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; const TEMPT_ITEMS: &[&Item] = &[&Item::WHEAT]; @@ -58,11 +58,11 @@ impl CowEntity { } impl NBTStorage for CowEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.write_nbt(nbt) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.read_nbt_non_mut(nbt) } } diff --git a/pumpkin/src/entity/passive/iron_golem.rs b/pumpkin/src/entity/passive/iron_golem.rs index 790e7dbe2..63d34e2b0 100644 --- a/pumpkin/src/entity/passive/iron_golem.rs +++ b/pumpkin/src/entity/passive/iron_golem.rs @@ -11,7 +11,7 @@ use crate::entity::{ }, mob::{Mob, MobEntity}, }; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; /// Represents an Iron Golem, a powerful neutral mob that protects villagers and players. /// @@ -58,11 +58,11 @@ impl IronGolemEntity { } impl NBTStorage for IronGolemEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.write_nbt(nbt) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.read_nbt_non_mut(nbt) } } diff --git a/pumpkin/src/entity/passive/pig.rs b/pumpkin/src/entity/passive/pig.rs index 1f8d26e42..56b2a529b 100644 --- a/pumpkin/src/entity/passive/pig.rs +++ b/pumpkin/src/entity/passive/pig.rs @@ -16,7 +16,7 @@ use crate::entity::{ mob::{Mob, MobEntity}, player::Player, }; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; const PIG_FOOD: &[&Item] = &[ &Item::CARROT, @@ -63,11 +63,11 @@ impl PigEntity { } impl NBTStorage for PigEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.write_nbt(nbt) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { self.mob_entity.living_entity.read_nbt_non_mut(nbt) } } diff --git a/pumpkin/src/entity/passive/sheep.rs b/pumpkin/src/entity/passive/sheep.rs index ddbe086f1..669d158fb 100644 --- a/pumpkin/src/entity/passive/sheep.rs +++ b/pumpkin/src/entity/passive/sheep.rs @@ -6,6 +6,7 @@ use std::sync::{ use pumpkin_data::{ entity::EntityType, item::Item, meta_data_type::MetaDataType, tracked_data::TrackedData, }; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::java::client::play::Metadata; use crate::entity::{ @@ -105,22 +106,24 @@ impl SheepEntity { } } -use pumpkin_nbt::pnbt::PNbtCompound; - impl NBTStorage for SheepEntity { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity.living_entity.write_nbt(nbt).await; - nbt.put_bool(self.is_sheared()); - nbt.put_byte(self.get_color() as i8); + self.mob_entity.living_entity.entity.write_nbt(nbt).await; + nbt.put_bool("Sheared", self.is_sheared()); + nbt.put_byte("Color", self.get_color() as i8); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - let sheared = nbt.get_bool().unwrap_or(false); - let color = nbt.get_byte().unwrap_or(0) as u8; + self.mob_entity + .living_entity + .entity + .read_nbt_non_mut(nbt) + .await; + let sheared = nbt.get_bool("Sheared").unwrap_or(false); + let color = nbt.get_byte("Color").unwrap_or(0) as u8; let byte = (color & 0x0F) | if sheared { 0x10 } else { 0 }; self.color_and_sheared.store(byte, Ordering::Relaxed); }) diff --git a/pumpkin/src/entity/passive/villager/mod.rs b/pumpkin/src/entity/passive/villager/mod.rs index 711640d33..aa349ae3f 100644 --- a/pumpkin/src/entity/passive/villager/mod.rs +++ b/pumpkin/src/entity/passive/villager/mod.rs @@ -11,6 +11,7 @@ use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler; use pumpkin_inventory::screen_handler::{ BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, }; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::{CMerchantOffers, Metadata}; use pumpkin_util::text::TextComponent; @@ -318,109 +319,138 @@ impl ScreenHandlerFactory for VillagerEntity { } impl NBTStorage for VillagerEntity { - fn write_nbt<'a>( - &'a self, - nbt: &'a mut pumpkin_nbt::pnbt::PNbtCompound, - ) -> crate::entity::NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> crate::entity::NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity.living_entity.write_nbt(nbt).await; + self.mob_entity.living_entity.entity.write_nbt(nbt).await; let data = self.villager_data.lock().await; - nbt.put_int(data.r#type as i32); - nbt.put_int(data.profession as i32); - nbt.put_int(data.level); + let mut villager_data_nbt = NbtCompound::new(); + villager_data_nbt.put_int("Type", data.r#type as i32); + villager_data_nbt.put_int("Profession", data.profession as i32); + villager_data_nbt.put_int("Level", data.level); + nbt.put_compound("VillagerData", villager_data_nbt); - nbt.put_int(self.food_level.load(Ordering::Relaxed)); - nbt.put_int(self.xp.load(Ordering::Relaxed)); - nbt.put_long(self.last_restock_time.load(Ordering::Relaxed)); - nbt.put_int(self.restocks_today.load(Ordering::Relaxed)); + nbt.put_int("FoodLevel", self.food_level.load(Ordering::Relaxed)); + nbt.put_int("Xp", self.xp.load(Ordering::Relaxed)); + nbt.put_long( + "LastRestock", + self.last_restock_time.load(Ordering::Relaxed), + ); + nbt.put_int("RestocksToday", self.restocks_today.load(Ordering::Relaxed)); // Inventory let inventory = self.inventory.lock().await; - nbt.put_int(inventory.len() as i32); + let mut inventory_list = Vec::new(); for stack_mutex in inventory.iter() { let stack = stack_mutex.lock().await; - stack.write_item_stack_pnbt(nbt); - } - - // Gossips (Simplified: just save counts per UUID and type) - let gossips = self.gossips.lock().await; - nbt.put_int(gossips.len() as i32); - for (uuid, types) in gossips.iter() { - nbt.put_uuid(uuid); - nbt.put_int(types.len() as i32); - for (gtype, value) in types { - nbt.put_int(*gtype as i32); - nbt.put_int(*value); + if !stack.is_empty() { + let mut item_nbt = NbtCompound::new(); + stack.write_item_stack(&mut item_nbt); + inventory_list.push(pumpkin_nbt::tag::NbtTag::Compound(item_nbt)); } } + nbt.put("Inventory", pumpkin_nbt::tag::NbtTag::List(inventory_list)); + + // Gossips + let gossips = self.gossips.lock().await; + let mut gossip_list = Vec::new(); + for (uuid, types) in gossips.iter() { + for (gtype, value) in types { + let mut gossip_nbt = NbtCompound::new(); + let uuid_val = uuid.as_u128(); + gossip_nbt.put( + "Target", + pumpkin_nbt::tag::NbtTag::IntArray(vec![ + (uuid_val >> 96) as i32, + ((uuid_val >> 64) & 0xFFFF_FFFF) as i32, + ((uuid_val >> 32) & 0xFFFF_FFFF) as i32, + (uuid_val & 0xFFFF_FFFF) as i32, + ]), + ); + gossip_nbt.put_int("Type", *gtype as i32); + gossip_nbt.put_int("Value", *value); + gossip_list.push(pumpkin_nbt::tag::NbtTag::Compound(gossip_nbt)); + } + } + nbt.put("Gossips", pumpkin_nbt::tag::NbtTag::List(gossip_list)); }) } - fn read_nbt_non_mut<'a>( - &'a self, - nbt: &'a mut pumpkin_nbt::pnbt::PNbtCompound, - ) -> crate::entity::NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> crate::entity::NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; - let mut data = self.villager_data.lock().await; - if let Ok(t) = nbt.get_int() { - data.r#type = VillagerType::try_from(t).unwrap_or(VillagerType::Plains); - } - if let Ok(p) = nbt.get_int() { - data.profession = - VillagerProfession::try_from(p).unwrap_or(VillagerProfession::None); - } - if let Ok(l) = nbt.get_int() { - data.level = l; + self.mob_entity + .living_entity + .entity + .read_nbt_non_mut(nbt) + .await; + if let Some(villager_data_nbt) = nbt.get_compound("VillagerData") { + let mut data = self.villager_data.lock().await; + if let Some(t) = villager_data_nbt.get_int("Type") { + data.r#type = VillagerType::try_from(t).unwrap_or(VillagerType::Plains); + } + if let Some(p) = villager_data_nbt.get_int("Profession") { + data.profession = + VillagerProfession::try_from(p).unwrap_or(VillagerProfession::None); + } + if let Some(l) = villager_data_nbt.get_int("Level") { + data.level = l; + } } - if let Ok(food) = nbt.get_int() { + if let Some(food) = nbt.get_int("FoodLevel") { self.food_level.store(food, Ordering::Relaxed); } - if let Ok(xp) = nbt.get_int() { + if let Some(xp) = nbt.get_int("Xp") { self.xp.store(xp, Ordering::Relaxed); } - if let Ok(restock) = nbt.get_long() { + if let Some(restock) = nbt.get_long("LastRestock") { self.last_restock_time.store(restock, Ordering::Relaxed); } - if let Ok(today) = nbt.get_int() { + if let Some(today) = nbt.get_int("RestocksToday") { self.restocks_today.store(today, Ordering::Relaxed); } // Inventory - if let Ok(inv_len) = nbt.get_int() { + if let Some(inventory_list) = nbt.get_list("Inventory") { let mut inventory = self.inventory.lock().await; inventory.clear(); - for _ in 0..inv_len { - let stack = - ItemStack::read_item_stack_pnbt(nbt).unwrap_or(ItemStack::EMPTY.clone()); - inventory.push(Arc::new(Mutex::new(stack))); + for tag in inventory_list { + if let Some(item_compound) = tag.extract_compound() + && let Some(stack) = ItemStack::read_item_stack(item_compound) + { + inventory.push(Arc::new(Mutex::new(stack))); + } } } // Gossips - if let Ok(gossip_len) = nbt.get_int() { + if let Some(gossip_list) = nbt.get_list("Gossips") { let mut gossips = self.gossips.lock().await; gossips.clear(); - for _ in 0..gossip_len { - if let Ok(uuid) = nbt.get_uuid() - && let Ok(types_len) = nbt.get_int() - { - let mut types = HashMap::new(); - for _ in 0..types_len { - if let (Ok(gtype), Ok(val)) = (nbt.get_int(), nbt.get_int()) { - let gossip_type = match gtype { - 0 => GossipType::MajorNegative, - 1 => GossipType::MinorNegative, - 2 => GossipType::MajorPositive, - 3 => GossipType::MinorPositive, - 4 => GossipType::Trading, - _ => continue, - }; - types.insert(gossip_type, val); - } + for tag in gossip_list { + if let Some(gossip_nbt) = tag.extract_compound() { + let uuid = gossip_nbt.get_int_array("Target").map(|uuid_array| { + Uuid::from_u128( + (uuid_array[0] as u128) << 96 + | (uuid_array[1] as u128) << 64 + | (uuid_array[2] as u128) << 32 + | (uuid_array[3] as u128), + ) + }); + if let (Some(uuid), Some(gtype), Some(val)) = ( + uuid, + gossip_nbt.get_int("Type"), + gossip_nbt.get_int("Value"), + ) { + let gossip_type = match gtype { + 0 => GossipType::MajorNegative, + 1 => GossipType::MinorNegative, + 2 => GossipType::MajorPositive, + 3 => GossipType::MinorPositive, + 4 => GossipType::Trading, + _ => continue, + }; + gossips.entry(uuid).or_default().insert(gossip_type, val); } - gossips.insert(uuid, types); } } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 6ff99e1dd..432701849 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -53,7 +53,8 @@ use pumpkin_inventory::screen_handler::{ }; use pumpkin_inventory::sync_handler::SyncHandler; use pumpkin_macros::send_cancellable; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; use pumpkin_protocol::IdOr; use pumpkin_protocol::SoundEvent; use pumpkin_protocol::codec::var_int::VarInt; @@ -3696,10 +3697,9 @@ impl PartialEq for Player { } impl NBTStorage for Player { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - nbt.put_int(DATA_VERSION); - nbt.put_string(self.world().dimension.minecraft_name); + nbt.put_int("DataVersion", DATA_VERSION); self.living_entity.write_nbt(nbt).await; self.inventory.write_nbt(nbt).await; self.ender_chest_inventory.write_nbt(nbt).await; @@ -3709,44 +3709,48 @@ impl NBTStorage for Player { let total_exp = experience::points_to_level(self.experience_level.load(Ordering::Relaxed)) + self.experience_points.load(Ordering::Relaxed); - nbt.put_int(total_exp); - - nbt.put_byte(self.gamemode.load() as i8); + nbt.put_int("XpTotal", total_exp); + nbt.put_byte("playerGameType", self.gamemode.load() as i8); if let Some(previous_gamemode) = self.previous_gamemode.load() { - nbt.put_bool(true); - nbt.put_byte(previous_gamemode as i8); - } else { - nbt.put_bool(false); + nbt.put_byte("previousPlayerGameType", previous_gamemode as i8); } - nbt.put_bool(self.has_played_before.load(Ordering::Relaxed)); + nbt.put_bool( + "HasPlayedBefore", + self.has_played_before.load(Ordering::Relaxed), + ); + + // Store food level, saturation, exhaustion, and tick timer self.hunger_manager.write_nbt(nbt).await; + nbt.put_string( + "Dimension", + self.world().dimension.minecraft_name.to_string(), + ); + if let Some(respawn) = self.respawn_point.lock().await.as_ref() { - nbt.put_bool(true); - nbt.put_int(respawn.position.0.x); - nbt.put_int(respawn.position.0.y); - nbt.put_int(respawn.position.0.z); - nbt.put_string(respawn.dimension.minecraft_name); - nbt.put_bool(respawn.force); - } else { - nbt.put_bool(false); + nbt.put_int("SpawnX", respawn.position.0.x); + nbt.put_int("SpawnY", respawn.position.0.y); + nbt.put_int("SpawnZ", respawn.position.0.z); + nbt.put_string( + "SpawnDimension", + respawn.dimension.minecraft_name.to_owned(), + ); + nbt.put_bool("SpawnForced", respawn.force); } - nbt.put_int(self.enchantment_seed.load(Ordering::Relaxed)); + nbt.put_int("XpSeed", self.enchantment_seed.load(Ordering::Relaxed)); }) } - fn read_nbt<'a>(&'a mut self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt<'a>(&'a mut self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - let _version = nbt.get_int().unwrap_or(0); - let _dimension_name = nbt.get_string().unwrap_or_default(); - self.living_entity.read_nbt(nbt).await; self.inventory.read_nbt_non_mut(nbt).await; self.ender_chest_inventory.read_nbt_non_mut(nbt).await; self.abilities.lock().await.read_nbt(nbt).await; - let total_exp = nbt.get_int().unwrap_or(0); + // Load from total XP + let total_exp = nbt.get_int("XpTotal").unwrap_or(0); let (level, points) = experience::total_to_level_and_points(total_exp); let progress = experience::progress_in_level(level, points); self.experience_level.store(level, Ordering::Relaxed); @@ -3754,29 +3758,33 @@ impl NBTStorage for Player { self.experience_points.store(points, Ordering::Relaxed); self.gamemode.store( - GameMode::try_from(nbt.get_byte().unwrap_or(0)).unwrap_or(GameMode::Survival), + GameMode::try_from(nbt.get_byte("playerGameType").unwrap_or(0)) + .unwrap_or(GameMode::Survival), ); - if nbt.get_bool().unwrap_or(false) { - self.previous_gamemode - .store(GameMode::try_from(nbt.get_byte().unwrap_or(0)).ok()); - } + self.previous_gamemode.store( + nbt.get_byte("previousPlayerGameType") + .and_then(|byte| GameMode::try_from(byte).ok()), + ); - self.has_played_before - .store(nbt.get_bool().unwrap_or(false), Ordering::Relaxed); + self.has_played_before.store( + nbt.get_bool("HasPlayedBefore").unwrap_or(false), + Ordering::Relaxed, + ); self.hunger_manager.read_nbt(nbt).await; - if nbt.get_bool().unwrap_or(false) { - let x = nbt.get_int().unwrap_or(0); - let y = nbt.get_int().unwrap_or(0); - let z = nbt.get_int().unwrap_or(0); + // Load any saved spawnpoint data (SpawnX/SpawnY/SpawnZ, SpawnDimension, SpawnForced) + if let (Some(x), Some(y), Some(z)) = ( + nbt.get_int("SpawnX"), + nbt.get_int("SpawnY"), + nbt.get_int("SpawnZ"), + ) { let dim = nbt - .get_string() - .ok() - .and_then(|s| Dimension::from_name(s.as_str()).cloned()) - .unwrap_or(self.world().dimension.clone()); - let force = nbt.get_bool().unwrap_or(false); + .get_string("SpawnDimension") + .and_then(|s| Dimension::from_name(s).cloned()) + .unwrap_or_else(|| self.world().dimension.clone()); + let force = nbt.get_bool("SpawnForced").unwrap_or(false); *self.respawn_point.lock().await = Some(RespawnPoint { dimension: dim, position: BlockPos(Vector3::new(x, y, z)), @@ -3784,8 +3792,10 @@ impl NBTStorage for Player { force, }); } - self.enchantment_seed - .store(nbt.get_int().unwrap_or(rand::random()), Ordering::Relaxed); + self.enchantment_seed.store( + nbt.get_int("XpSeed").unwrap_or(rand::random()), + Ordering::Relaxed, + ); }) } } @@ -3793,76 +3803,106 @@ impl NBTStorage for Player { impl NBTStorageInit for Player {} impl NBTStorage for PlayerInventory { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { // Save the selected slot (hotbar) - nbt.put_u8(self.get_selected_slot()); + nbt.put_int("SelectedItemSlot", i32::from(self.get_selected_slot())); - // Save items count then items - let mut present_items = Vec::new(); + // Create inventory list with the correct capacity (inventory size) + let mut items: Vec = Vec::with_capacity(41); for (i, item) in self.main_inventory.iter().enumerate() { let stack = item.lock().await; if !stack.is_empty() { - present_items.push((i as u8, stack.clone())); + let mut item_compound = NbtCompound::new(); + item_compound.put_byte("Slot", i as i8); + stack.write_item_stack(&mut item_compound); + drop(stack); + items.push(NbtTag::Compound(item_compound)); } } - nbt.put_u32(present_items.len() as u32); - for (slot, stack) in present_items { - nbt.put_u8(slot); - stack.write_item_stack_pnbt(nbt); - } - // Save equipment - #[allow(clippy::default_trait_access)] - for slot in [ - EquipmentSlot::OffHand(Default::default()), - EquipmentSlot::Head(Default::default()), - EquipmentSlot::Chest(Default::default()), - EquipmentSlot::Legs(Default::default()), - EquipmentSlot::Feet(Default::default()), - ] { - // Find the actual slot in self.equipment_slots - let mut found = false; - for s in self.equipment_slots.values() { - if mem::discriminant(s) == mem::discriminant(&slot) { - let stack_binding = self.entity_equipment.lock().await.get(s); - let stack = stack_binding.lock().await; - if !stack.is_empty() { - nbt.put_bool(true); - stack.write_item_stack_pnbt(nbt); - found = true; + let mut equipment_compound = NbtCompound::new(); + for slot in self.equipment_slots.values() { + let stack_binding = self.entity_equipment.lock().await.get(slot); + let stack = stack_binding.lock().await; + if !stack.is_empty() { + let mut item_compound = NbtCompound::new(); + stack.write_item_stack(&mut item_compound); + drop(stack); + match slot { + EquipmentSlot::OffHand(_) => { + equipment_compound.put_compound("offhand", item_compound); + } + EquipmentSlot::Feet(_) => { + equipment_compound.put_compound("feet", item_compound); + } + EquipmentSlot::Legs(_) => { + equipment_compound.put_compound("legs", item_compound); + } + EquipmentSlot::Chest(_) => { + equipment_compound.put_compound("chest", item_compound); + } + EquipmentSlot::Head(_) => { + equipment_compound.put_compound("head", item_compound); + } + _ => { + warn!("Invalid equipment slot for a player"); } - break; } } - if !found { - nbt.put_bool(false); - } } + nbt.put_compound("equipment", equipment_compound); + nbt.put("Inventory", NbtTag::List(items)); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { - Box::pin(async move { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { + Box::pin(async { // Read selected hotbar slot - self.set_selected_slot(nbt.get_u8().unwrap_or(0)); - + self.set_selected_slot(nbt.get_int("SelectedItemSlot").unwrap_or(0) as u8); // Process inventory list - let items_len = nbt.get_u32().unwrap_or(0) as usize; - for _ in 0..items_len { - let slot = nbt.get_u8().unwrap_or(0) as usize; - if let Some(item_stack) = ItemStack::read_item_stack_pnbt(nbt) { - self.set_stack(slot, item_stack).await; + if let Some(inventory_list) = nbt.get_list("Inventory") { + for tag in inventory_list { + if let Some(item_compound) = tag.extract_compound() + && let Some(slot_byte) = item_compound.get_byte("Slot") + { + let slot = slot_byte as usize; + if let Some(item_stack) = ItemStack::read_item_stack(item_compound) { + self.set_stack(slot, item_stack).await; + } + } } } - // Read equipment - for slot_idx in [40, 39, 38, 37, 36] { - // offhand, head, chest, legs, feet - if nbt.get_bool().unwrap_or(false) - && let Some(item_stack) = ItemStack::read_item_stack_pnbt(nbt) + if let Some(equipment) = nbt.get_compound("equipment") { + if let Some(offhand) = equipment.get_compound("offhand") + && let Some(item_stack) = ItemStack::read_item_stack(offhand) { - self.set_stack(slot_idx, item_stack).await; + self.set_stack(40, item_stack).await; + } + + if let Some(head) = equipment.get_compound("head") + && let Some(item_stack) = ItemStack::read_item_stack(head) + { + self.set_stack(39, item_stack).await; + } + + if let Some(chest) = equipment.get_compound("chest") + && let Some(item_stack) = ItemStack::read_item_stack(chest) + { + self.set_stack(38, item_stack).await; + } + + if let Some(legs) = equipment.get_compound("legs") + && let Some(item_stack) = ItemStack::read_item_stack(legs) + { + self.set_stack(37, item_stack).await; + } + + if let Some(feet) = equipment.get_compound("feet") + && let Some(item_stack) = ItemStack::read_item_stack(feet) + { + self.set_stack(36, item_stack).await; } } }) @@ -3872,32 +3912,38 @@ impl NBTStorage for PlayerInventory { impl NBTStorageInit for PlayerInventory {} impl NBTStorage for EnderChestInventory { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - // Save items count then items - let mut present_items = Vec::new(); + // Create item list with the correct capacity (inventory size) + let mut items: Vec = Vec::with_capacity(Self::INVENTORY_SIZE); for (i, item) in self.items.iter().enumerate() { let stack = item.lock().await; if !stack.is_empty() { - present_items.push((i as u8, stack.clone())); + let mut item_compound = NbtCompound::new(); + item_compound.put_byte("Slot", i as i8); + stack.write_item_stack(&mut item_compound); + drop(stack); + items.push(NbtTag::Compound(item_compound)); } } - nbt.put_u32(present_items.len() as u32); - for (slot, stack) in present_items { - nbt.put_u8(slot); - stack.write_item_stack_pnbt(nbt); - } + + nbt.put("EnderItems", NbtTag::List(items)); }) } - fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { // Process item list - let items_len = nbt.get_u32().unwrap_or(0) as usize; - for _ in 0..items_len { - let slot = nbt.get_u8().unwrap_or(0) as usize; - if let Some(item_stack) = ItemStack::read_item_stack_pnbt(nbt) { - self.set_stack(slot, item_stack).await; + if let Some(item_list) = nbt.get_list("EnderItems") { + for tag in item_list { + if let Some(item_compound) = tag.extract_compound() + && let Some(slot_byte) = item_compound.get_byte("Slot") + { + let slot = slot_byte as usize; + if let Some(item_stack) = ItemStack::read_item_stack(item_compound) { + self.set_stack(slot, item_stack).await; + } + } } } }) @@ -4079,27 +4125,31 @@ pub struct Abilities { } impl NBTStorage for Abilities { - fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - nbt.put_bool(self.invulnerable); - nbt.put_bool(self.flying); - nbt.put_bool(self.allow_flying); - nbt.put_bool(self.creative); - nbt.put_bool(self.allow_modify_world); - nbt.put_float(self.fly_speed); - nbt.put_float(self.walk_speed); + let mut component = NbtCompound::new(); + component.put_bool("invulnerable", self.invulnerable); + component.put_bool("flying", self.flying); + component.put_bool("mayfly", self.allow_flying); + component.put_bool("instabuild", self.creative); + component.put_bool("mayBuild", self.allow_modify_world); + component.put_float("flySpeed", self.fly_speed); + component.put_float("walkSpeed", self.walk_speed); + nbt.put_compound("abilities", component); }) } - fn read_nbt<'a>(&'a mut self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { + fn read_nbt<'a>(&'a mut self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.invulnerable = nbt.get_bool().unwrap_or(false); - self.flying = nbt.get_bool().unwrap_or(false); - self.allow_flying = nbt.get_bool().unwrap_or(false); - self.creative = nbt.get_bool().unwrap_or(false); - self.allow_modify_world = nbt.get_bool().unwrap_or(false); - self.fly_speed = nbt.get_float().unwrap_or(0.05); - self.walk_speed = nbt.get_float().unwrap_or(0.1); + if let Some(component) = nbt.get_compound("abilities") { + self.invulnerable = component.get_bool("invulnerable").unwrap_or(false); + self.flying = component.get_bool("flying").unwrap_or(false); + self.allow_flying = component.get_bool("mayfly").unwrap_or(false); + self.creative = component.get_bool("instabuild").unwrap_or(false); + self.allow_modify_world = component.get_bool("mayBuild").unwrap_or(false); + self.fly_speed = component.get_float("flySpeed").unwrap_or(0.05); + self.walk_speed = component.get_float("walkSpeed").unwrap_or(0.1); + } }) } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 734ec8740..29d3fe29d 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -442,13 +442,10 @@ impl Server { let gamemode = self.defaultgamemode.lock().await.gamemode; let (world, nbt) = - if let Ok(Some(mut data)) = self.player_data_storage.load_data(&profile.id).await { - let _version = data.get_int().unwrap_or(0); - if let Ok(dimension_key) = data.get_string() { - if let Some(dimension) = Dimension::from_name(&dimension_key) { + if let Ok(Some(data)) = self.player_data_storage.load_data(&profile.id).await { + if let Some(dimension_key) = data.get_string("Dimension") { + if let Some(dimension) = Dimension::from_name(dimension_key) { let world = self.get_world_from_dimension(dimension); - // Reset read position so player.read_nbt can read everything from start - data.read_pos = 0; (world, Some(data)) } else { warn!("Invalid dimension key in player data: {dimension_key}"); @@ -458,18 +455,16 @@ impl Server { .first() .expect("Default world should exist") .clone(); - data.read_pos = 0; (default_world, Some(data)) } } else { - // Player data exists but doesn't have a dimension entry. + // Player data exists but doesn't have a "Dimension" key. let default_world = self .worlds .load() .first() .expect("Default world should exist") .clone(); - data.read_pos = 0; (default_world, Some(data)) } } else { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index fa4e9aecf..547b7e62c 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -66,8 +66,7 @@ use pumpkin_data::{ }; use pumpkin_data::{BlockDirection, BlockState, translation}; use pumpkin_inventory::screen_handler::InventoryPlayer; -use pumpkin_nbt::pnbt::PNbtCompound; -use pumpkin_nbt::to_bytes_unnamed; +use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; use pumpkin_protocol::bedrock::client::set_actor_data::{ CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag, entity_data_key, @@ -335,7 +334,7 @@ impl World { let base_entity = entity.get_entity(); let uuid = base_entity.entity_uuid; let current_chunk_coordinate = base_entity.block_pos.load().chunk_position(); - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); entity.write_nbt(&mut nbt).await; if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); @@ -2670,46 +2669,34 @@ impl World { }; let position = Vector2::new(chunk.x, chunk.z); - let chunk = if level.is_chunk_watched(&position) { - chunk - } else { + if !level.is_chunk_watched(&position) { trace!( "Received chunk {:?}, but it is no longer watched... cleaning", &position ); let mut ids_to_remove = Vec::new(); - let mut entities_to_load = Vec::new(); - let mut entity_data = Vec::new(); - { - let mut data = chunk.data.lock().await; - for (uuid, entity_nbt) in data.iter_mut() { - let Ok(id) = entity_nbt.get_string() else { - warn!("Entity has no ID"); - continue; - }; - let Some(entity_type) = EntityType::from_name(&id) else { - warn!("Entity has no valid Entity Type {id}"); - continue; - }; - entity_data.push((*uuid, entity_type, entity_nbt.clone())); - } - } - - for (uuid, entity_type, mut entity_nbt) in entity_data { + for (uuid, entity_nbt) in chunk.data.lock().await.iter() { + let Some(id) = entity_nbt.get_string("id") else { + warn!("Entity has no ID"); + continue; + }; + let Some(entity_type) = + EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id)) + else { + warn!("Entity has no valid Entity Type {id}"); + continue; + }; // Pos is zero since it will read from nbt let entity = - from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, uuid).await; - entity_nbt.read_pos = 0; - entity.read_nbt_non_mut(&mut entity_nbt).await; - entities_to_load.push(entity); - } - - for entity in entities_to_load { + from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, *uuid) + .await; + entity.read_nbt_non_mut(entity_nbt).await; let base_entity = entity.get_entity(); + ids_to_remove.push(VarInt(base_entity.entity_id)); - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); entity.write_nbt(&mut nbt).await; if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); @@ -2721,65 +2708,40 @@ impl World { let mut data = chunk.data.lock().await; if old_chunk == current_chunk_coordinate { - data.insert(base_entity.entity_uuid, nbt.clone()); - return; + data.insert(*uuid, nbt); + continue; } // The chunk has changed, lets remove the entity from the old chunk - data.remove(&base_entity.entity_uuid); + data.remove(uuid); } - chunk - .data - .lock() - .await - .insert(base_entity.entity_uuid, nbt.clone()); - chunk.mark_dirty(true); } - if !ids_to_remove.is_empty() { - world.entities.rcu(|current_entities| { - let mut new_entities = (**current_entities).clone(); - new_entities.retain(|e| { - !ids_to_remove.contains(&VarInt(e.get_entity().entity_id)) - }); - new_entities - }); player .client .enqueue_packet(&CRemoveEntities::new(&ids_to_remove)) .await; } - level.clean_entity_chunk(&position); - continue 'main; - }; + } // Add all new Entities to the world let mut entities_to_add: Vec> = Vec::new(); - let mut entity_data = Vec::new(); - { - let mut data = chunk.data.lock().await; - for (uuid, entity_nbt) in data.iter_mut() { - let Ok(id) = entity_nbt.get_string() else { - debug!("Entity has no ID"); - continue; - }; - let Some(entity_type) = - EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(&id)) - else { - warn!("Entity has no valid Entity Type {id}"); - continue; - }; - entity_data.push((*uuid, entity_type, entity_nbt.clone())); - } - } - - for (uuid, entity_type, mut entity_nbt) in entity_data { + for (uuid, entity_nbt) in chunk.data.lock().await.iter() { + let Some(id) = entity_nbt.get_string("id") else { + debug!("Entity has no ID"); + continue; + }; + let Some(entity_type) = + EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id)) + else { + warn!("Entity has no valid Entity Type {id}"); + continue; + }; // Pos is zero since it will read from nbt let entity = - from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, uuid).await; - entity_nbt.read_pos = 0; - entity.read_nbt_non_mut(&mut entity_nbt).await; + from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, *uuid).await; + entity.read_nbt_non_mut(entity_nbt).await; let base_entity = entity.get_entity(); player .client @@ -3215,7 +3177,7 @@ impl World { let block_pos = base_entity.block_pos.load(); let chunk_coordinate = block_pos.chunk_position(); - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); entity.write_nbt(&mut nbt).await; self.spawn_state.load().add_entity(self, entity.as_ref()); diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 612d1febb..ddcda8d72 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -10,7 +10,7 @@ use pumpkin_data::tag::Fluid::{MINECRAFT_LAVA, MINECRAFT_WATER}; use pumpkin_data::tag::Taggable; use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS; use pumpkin_data::{Block, BlockDirection, BlockState}; -use pumpkin_nbt::pnbt::PNbtCompound; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::GameMode; use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions}; use pumpkin_util::math::get_section_cord; @@ -716,7 +716,7 @@ pub async fn spawn_category_for_position( entity.init_data_tracker().await; let base_entity = entity.get_entity(); let packet = base_entity.create_spawn_packet(); - let mut nbt = PNbtCompound::new(); + let mut nbt = NbtCompound::new(); entity.write_nbt(&mut nbt).await; // Keep the entity reference here so we don't have to "find" it later prepared_data.push((base_entity.entity_uuid, nbt, packet, entity.clone()));