Replace serde_nbt with valence_nbt (#80)

valence_nbt has a much nicer API and avoids the complications brought by integrating with serde. valence_nbt also fixes some bugs and is 3x faster according to benchmarks.
This commit is contained in:
Ryan Johnson 2022-09-23 04:03:21 -07:00 committed by GitHub
parent cc9cd0be2d
commit 36b63e777e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 222 additions and 431 deletions

View file

@ -36,7 +36,7 @@ rsa = "0.6.1"
rsa-der = "0.3.0"
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.85"
serde_nbt = "0.1.1"
valence_nbt = "0.1.2"
sha1 = "0.10.5"
sha2 = "0.10.6"
thiserror = "1.0.35"

View file

@ -1,8 +1,9 @@
//! Biome configuration and identification.
use valence_nbt::{compound, Compound};
use crate::ident;
use crate::ident::Ident;
use crate::protocol::packets::s2c::play::Biome as BiomeRegistryBiome;
/// Identifies a particular [`Biome`] on the server.
///
@ -47,66 +48,91 @@ pub struct Biome {
}
impl Biome {
pub(crate) fn to_biome_registry_item(&self, id: i32) -> BiomeRegistryBiome {
use crate::protocol::packets::s2c::play::{
BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle,
BiomeParticleOptions, BiomeProperty,
};
BiomeRegistryBiome {
name: self.name.clone(),
id,
element: BiomeProperty {
precipitation: match self.precipitation {
pub(crate) fn to_biome_registry_item(&self, id: i32) -> Compound {
let mut reg = compound! {
"name" => self.name.clone(),
"id" => id,
"element" => compound! {
"precipitation" => match self.precipitation {
BiomePrecipitation::Rain => "rain",
BiomePrecipitation::Snow => "snow",
BiomePrecipitation::None => "none",
}
.into(),
depth: 0.125,
temperature: 0.8,
scale: 0.05,
downfall: 0.4,
category: "none".into(),
temperature_modifier: None,
effects: BiomeEffects {
sky_color: self.sky_color as i32,
water_fog_color: self.water_fog_color as i32,
fog_color: self.fog_color as i32,
water_color: self.water_color as i32,
foliage_color: self.foliage_color.map(|x| x as i32),
grass_color: self.grass_color.map(|x| x as i32),
grass_color_modifier: match self.grass_color_modifier {
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
BiomeGrassColorModifier::None => None,
},
music: self.music.as_ref().map(|bm| BiomeMusic {
replace_current_music: bm.replace_current_music,
sound: bm.sound.clone(),
max_delay: bm.max_delay,
min_delay: bm.min_delay,
}),
ambient_sound: self.ambient_sound.clone(),
additions_sound: self.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
sound: a.sound.clone(),
tick_chance: a.tick_chance,
}),
mood_sound: self.mood_sound.as_ref().map(|m| BiomeMoodSound {
sound: m.sound.clone(),
tick_delay: m.tick_delay,
offset: m.offset,
block_search_extent: m.block_search_extent,
}),
},
particle: self.particle.as_ref().map(|p| BiomeParticle {
probability: p.probability,
options: BiomeParticleOptions {
kind: p.kind.clone(),
},
}),
},
"depth" => 0.125_f32,
"temperature" => 0.8_f32,
"scale" => 0.05_f32,
"downfall" => 0.4_f32,
"category" => "none",
// "temperature_modifier" =>
"effects" => {
let mut eff = compound! {
"sky_color" => self.sky_color as i32,
"water_fog_color" => self.water_fog_color as i32,
"fog_color" => self.fog_color as i32,
"water_color" => self.water_color as i32,
};
if let Some(color) = self.foliage_color {
eff.insert("foliage_color", color as i32);
}
if let Some(color) = self.grass_color {
eff.insert("grass_color", color as i32);
}
match self.grass_color_modifier {
BiomeGrassColorModifier::Swamp => eff.insert("grass_color_modifier", "swamp"),
BiomeGrassColorModifier::DarkForest => eff.insert("grass_color_modifier", "dark_forest"),
BiomeGrassColorModifier::None => None
};
if let Some(music) = &self.music {
eff.insert("music", compound! {
"replace_current_music" => music.replace_current_music,
"sound" => music.sound.clone(),
"max_delay" => music.max_delay,
"min_delay" => music.min_delay,
});
}
if let Some(s) = &self.ambient_sound {
eff.insert("ambient_sound", s.clone());
}
if let Some(a) = &self.additions_sound {
eff.insert("additions_sound", compound! {
"sound" => a.sound.clone(),
"tick_chance" => a.tick_chance,
});
}
if let Some(m) = &self.mood_sound {
eff.insert("mood_sound", compound! {
"sound" => m.sound.clone(),
"tick_delay" => m.tick_delay,
"offset" => m.offset,
"block_search_extent" => m.block_search_extent,
});
}
eff
},
}
};
if let Some(p) = &self.particle {
reg.insert(
"particle",
compound! {
"probability" => p.probability,
"options" => compound! {
"type" => p.kind.clone(),
}
},
);
}
reg
}
}
@ -114,14 +140,14 @@ impl Default for Biome {
fn default() -> Self {
Self {
name: ident!("plains"),
precipitation: BiomePrecipitation::Rain,
precipitation: BiomePrecipitation::default(),
sky_color: 7907327,
water_fog_color: 329011,
fog_color: 12638463,
water_color: 4159204,
foliage_color: None,
grass_color: None,
grass_color_modifier: BiomeGrassColorModifier::None,
grass_color_modifier: BiomeGrassColorModifier::default(),
music: None,
ambient_sound: None,
additions_sound: None,

View file

@ -16,6 +16,7 @@ use std::iter::FusedIterator;
use bitvec::vec::BitVec;
use num::Integer;
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence_nbt::compound;
use crate::biome::BiomeId;
use crate::block::BlockState;
@ -24,9 +25,9 @@ pub use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::protocol::packets::s2c::play::{
BlockUpdate, ChunkDataAndUpdateLight, ChunkDataHeightmaps, S2cPlayPacket, UpdateSectionBlocks,
BlockUpdate, ChunkDataAndUpdateLight, S2cPlayPacket, UpdateSectionBlocks,
};
use crate::protocol::{Encode, NbtBridge, VarInt, VarLong};
use crate::protocol::{Encode, VarInt, VarLong};
use crate::server::SharedServer;
/// A container for all [`LoadedChunk`]s in a [`World`](crate::world::World).
@ -478,9 +479,9 @@ impl<C: Config> LoadedChunk<C> {
ChunkDataAndUpdateLight {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: NbtBridge(ChunkDataHeightmaps {
motion_blocking: self.heightmap.clone(),
}),
heightmaps: compound! {
"MOTION_BLOCKING" => self.heightmap.clone(),
},
blocks_and_biomes,
block_entities: Vec::new(), // TODO
trust_edges: true,

View file

@ -10,6 +10,7 @@ pub use event::*;
use flume::{Receiver, Sender, TrySendError};
use rayon::iter::ParallelIterator;
use uuid::Uuid;
use valence_nbt::{compound, Compound, List};
use vek::Vec3;
use crate::biome::Biome;
@ -27,17 +28,16 @@ use crate::player_textures::SignedPlayerTextures;
use crate::protocol::packets::c2s::play::{self, C2sPlayPacket, InteractKind, PlayerCommandId};
pub use crate::protocol::packets::s2c::play::SetTitleAnimationTimes;
use crate::protocol::packets::s2c::play::{
AcknowledgeBlockChange, BiomeRegistry, ChatTypeRegistry, ClearTitles, CustomSoundEffect,
DimensionTypeRegistry, DimensionTypeRegistryEntry, DisconnectPlay, EntityAnimationS2c,
AcknowledgeBlockChange, ClearTitles, CustomSoundEffect, DisconnectPlay, EntityAnimationS2c,
EntityAttributesProperty, EntityEvent, GameEvent, GameStateChangeReason, KeepAliveS2c,
LoginPlay, PlayerPositionLookFlags, RegistryCodec, RemoveEntities, ResourcePackS2c, Respawn,
S2cPlayPacket, SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
LoginPlay, PlayerPositionLookFlags, RemoveEntities, ResourcePackS2c, Respawn, S2cPlayPacket,
SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
SetEntityVelocity, SetExperience, SetHeadRotation, SetHealth, SetRenderDistance,
SetSubtitleText, SetTitleText, SoundCategory, SynchronizePlayerPosition, SystemChatMessage,
TeleportEntity, UnloadChunk, UpdateAttributes, UpdateEntityPosition,
UpdateEntityPositionAndRotation, UpdateEntityRotation, UpdateTime,
};
use crate::protocol::{BoundedInt, BoundedString, ByteAngle, NbtBridge, RawBytes, VarInt};
use crate::protocol::{BoundedInt, BoundedString, ByteAngle, RawBytes, VarInt};
use crate::server::{C2sPacketChannels, NewClientData, S2cPlayMessage, SharedServer};
use crate::slab_versioned::{Key, VersionedSlab};
use crate::slot::Slot;
@ -1058,7 +1058,7 @@ impl<C: Config> Client<C> {
let mut dimension_names: Vec<_> = shared
.dimensions()
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
.map(|(id, _)| id.dimension_name())
.collect();
dimension_names.push(ident!("{LIBRARY_NAMESPACE}:dummy_dimension"));
@ -1069,15 +1069,9 @@ impl<C: Config> Client<C> {
gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode,
dimension_names,
registry_codec: NbtBridge(make_registry_codec(shared)),
dimension_type_name: ident!(
"{LIBRARY_NAMESPACE}:dimension_type_{}",
world.meta.dimension().0
),
dimension_name: ident!(
"{LIBRARY_NAMESPACE}:dimension_{}",
world.meta.dimension().0
),
registry_codec: make_registry_codec(shared),
dimension_type_name: world.meta.dimension().dimension_type_name(),
dimension_name: world.meta.dimension().dimension_name(),
hashed_seed: 0,
max_players: VarInt(0),
view_distance: BoundedInt(VarInt(self.view_distance() as i32)),
@ -1088,7 +1082,7 @@ impl<C: Config> Client<C> {
is_flat: self.bits.flat(),
last_death_location: self
.death_location
.map(|(id, pos)| (ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0), pos)),
.map(|(id, pos)| (id.dimension_name(), pos)),
});
self.teleport(self.position(), self.yaw(), self.pitch());
@ -1101,7 +1095,7 @@ impl<C: Config> Client<C> {
// Client bug workaround: send the client to a dummy dimension first.
// TODO: is there actually a bug?
self.send_packet(Respawn {
dimension_type_name: ident!("{LIBRARY_NAMESPACE}:dimension_type_0"),
dimension_type_name: DimensionId(0).dimension_type_name(),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"),
hashed_seed: 0,
game_mode: self.game_mode(),
@ -1113,14 +1107,8 @@ impl<C: Config> Client<C> {
});
self.send_packet(Respawn {
dimension_type_name: ident!(
"{LIBRARY_NAMESPACE}:dimension_type_{}",
world.meta.dimension().0
),
dimension_name: ident!(
"{LIBRARY_NAMESPACE}:dimension_{}",
world.meta.dimension().0
),
dimension_type_name: world.meta.dimension().dimension_type_name(),
dimension_name: world.meta.dimension().dimension_name(),
hashed_seed: 0,
game_mode: self.game_mode(),
previous_game_mode: self.game_mode(),
@ -1129,7 +1117,7 @@ impl<C: Config> Client<C> {
copy_metadata: true,
last_death_location: self
.death_location
.map(|(id, pos)| (ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0), pos)),
.map(|(id, pos)| (id.dimension_name(), pos)),
});
self.teleport(self.position(), self.yaw(), self.pitch());
@ -1549,44 +1537,40 @@ fn send_entity_events(send_opt: &mut SendOpt, entity_id: i32, events: &[entity::
}
}
fn make_registry_codec<C: Config>(shared: &SharedServer<C>) -> RegistryCodec {
let mut dims = Vec::new();
for (id, dim) in shared.dimensions() {
let id = id.0 as i32;
dims.push(DimensionTypeRegistryEntry {
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
id,
element: dim.to_dimension_registry_item(),
})
}
let mut biomes: Vec<_> = shared
.biomes()
.map(|(id, biome)| biome.to_biome_registry_item(id.0 as i32))
.collect();
// The client needs a biome named "minecraft:plains" in the registry to
// connect. This is probably a bug.
//
// If the issue is resolved, just delete this block.
if !biomes.iter().any(|b| b.name == ident!("plains")) {
let biome = Biome::default();
assert_eq!(biome.name, ident!("plains"));
biomes.push(biome.to_biome_registry_item(biomes.len() as i32));
}
RegistryCodec {
dimension_type_registry: DimensionTypeRegistry {
kind: ident!("dimension_type"),
value: dims,
fn make_registry_codec<C: Config>(shared: &SharedServer<C>) -> Compound {
compound! {
ident!("dimension_type") => compound! {
"type" => ident!("dimension_type"),
"value" => List::Compound(shared.dimensions().map(|(id, dim)| compound! {
"name" => id.dimension_type_name(),
"id" => id.0 as i32,
"element" => dim.to_dimension_registry_item(),
}).collect()),
},
biome_registry: BiomeRegistry {
kind: ident!("worldgen/biome"),
value: biomes,
ident!("worldgen/biome") => compound! {
"type" => ident!("worldgen/biome"),
"value" => {
let mut biomes: Vec<_> = shared
.biomes()
.map(|(id, biome)| biome.to_biome_registry_item(id.0 as i32))
.collect();
// The client needs a biome named "minecraft:plains" in the registry to
// connect. This is probably a bug in the client.
//
// If the issue is resolved, remove this if.
if !biomes.iter().any(|b| b["name"] == "plains".into()) {
let biome = Biome::default();
assert_eq!(biome.name, ident!("plains"));
biomes.push(biome.to_biome_registry_item(biomes.len() as i32));
}
List::Compound(biomes)
}
},
chat_type_registry: ChatTypeRegistry {
kind: ident!("chat_type"),
value: Vec::new(),
ident!("chat_type_registry") => compound! {
"type" => ident!("chat_type"),
"value" => List::Compound(Vec::new()),
},
}
}

View file

@ -1,6 +1,6 @@
use std::time::Duration;
use serde_nbt::Compound;
use valence_nbt::Compound;
use vek::Vec3;
use super::Client;

View file

@ -1,7 +1,9 @@
//! Dimension configuration and identification.
use crate::ident;
use crate::protocol::packets::s2c::play::DimensionType;
use valence_nbt::{compound, Compound};
use crate::ident::Ident;
use crate::{ident, LIBRARY_NAMESPACE};
/// Identifies a particular [`Dimension`] on the server.
///
@ -13,6 +15,16 @@ use crate::protocol::packets::s2c::play::DimensionType;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DimensionId(pub(crate) u16);
impl DimensionId {
pub(crate) fn dimension_type_name(self) -> Ident {
ident!("{LIBRARY_NAMESPACE}:dimension_type_{}", self.0)
}
pub(crate) fn dimension_name(self) -> Ident {
ident!("{LIBRARY_NAMESPACE}:dimension_{}", self.0)
}
}
/// The default dimension ID corresponds to the first element in the `Vec`
/// returned by [`crate::config::Config::dimensions`].
impl Default for DimensionId {
@ -72,31 +84,36 @@ pub struct Dimension {
}
impl Dimension {
pub(crate) fn to_dimension_registry_item(&self) -> DimensionType {
DimensionType {
piglin_safe: true,
has_raids: true,
monster_spawn_light_level: 0,
monster_spawn_block_light_limit: 0,
natural: self.natural,
ambient_light: self.ambient_light,
fixed_time: self.fixed_time.map(|t| t as i64),
infiniburn: "#minecraft:infiniburn_overworld".into(),
respawn_anchor_works: true,
has_skylight: true,
bed_works: true,
effects: match self.effects {
DimensionEffects::Overworld => ident!("overworld"),
DimensionEffects::TheNether => ident!("the_nether"),
DimensionEffects::TheEnd => ident!("the_end"),
pub(crate) fn to_dimension_registry_item(&self) -> Compound {
let mut item = compound! {
"piglin_safe" => true,
"has_raids" => true,
"monster_spawn_light_level" => 0,
"monster_spawn_block_light_limit" => 0,
"natural" => self.natural,
"ambient_light" => self.ambient_light,
"infiniburn" => "#minecraft:infiniburn_overworld",
"respawn_anchor_works" => true,
"has_skylight" => true,
"bed_works" => true,
"effects" => match self.effects {
DimensionEffects::Overworld => "overworld",
DimensionEffects::TheNether => "the_nether",
DimensionEffects::TheEnd => "the_end",
},
min_y: self.min_y,
height: self.height,
logical_height: self.height,
coordinate_scale: 1.0,
ultrawarm: false,
has_ceiling: false,
"min_y" => self.min_y,
"height" => self.height,
"logical_height" => self.height,
"coordinate_scale" => 1.0,
"ultrawarm" => false,
"has_ceiling" => false,
};
if let Some(t) = self.fixed_time {
item.insert("fixed_time", t as i64);
}
item
}
}

View file

@ -1,6 +1,7 @@
//! Namespaced identifiers.
use std::borrow::Cow;
use std::hash;
use std::io::Write;
use std::str::FromStr;
@ -9,6 +10,7 @@ use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::nbt;
use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode};
/// An identifier is a string split into a "namespace" part and a "path" part.
@ -111,7 +113,7 @@ impl Ident {
}
}
/// Returns the original string as a `str`.
/// Returns the underlying string as a `str`.
pub fn as_str(&self) -> &str {
self.ident.as_str()
}
@ -157,6 +159,12 @@ impl From<Ident> for Cow<'static, str> {
}
}
impl From<Ident> for nbt::Value {
fn from(id: Ident) -> Self {
Self::String(id.into())
}
}
impl AsRef<str> for Ident {
fn as_ref(&self) -> &str {
self.as_str()
@ -194,8 +202,8 @@ impl PartialEq for Ident {
}
}
impl std::hash::Hash for Ident {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
impl hash::Hash for Ident {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.namespace().unwrap_or("minecraft").hash(state);
self.path().hash(state);
}
@ -252,7 +260,7 @@ impl<'de> Visitor<'de> for IdentifierVisitor {
/// # Panics
///
/// The macro will cause a panic if the formatted string is not a valid
/// identifier.
/// identifier. See [`Ident`] for more information.
///
/// # Examples
///
@ -279,6 +287,9 @@ macro_rules! ident {
#[cfg(test)]
mod tests {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[test]
fn parse_valid() {
ident!("minecraft:whatever");
@ -307,4 +318,15 @@ mod tests {
fn equality() {
assert_eq!(ident!("minecraft:my.identifier"), ident!("my.identifier"));
}
#[test]
fn equal_hash() {
let mut h1 = DefaultHasher::new();
ident!("minecraft:my.identifier").hash(&mut h1);
let mut h2 = DefaultHasher::new();
ident!("my.identifier").hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
}
}

View file

@ -87,7 +87,7 @@ pub use async_trait::async_trait;
#[doc(inline)]
pub use server::start_server;
#[doc(inline)]
pub use {serde_nbt as nbt, uuid, vek};
pub use {uuid, valence_nbt as nbt, vek};
pub mod biome;
pub mod block;

View file

@ -10,9 +10,8 @@ use arrayvec::ArrayVec;
use bitvec::prelude::*;
pub use byte_angle::ByteAngle;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::de::DeserializeOwned;
use serde::Serialize;
use uuid::Uuid;
use valence_nbt::Compound;
pub use var_int::VarInt;
pub use var_long::VarLong;
use vek::{Vec2, Vec3, Vec4};
@ -508,32 +507,16 @@ impl Decode for Uuid {
}
}
impl Encode for nbt::Compound {
impl Encode for Compound {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
Ok(nbt::binary::to_writer(w, self)?)
Ok(nbt::to_binary_writer(w, self, "")?)
}
}
impl Decode for nbt::Compound {
impl Decode for Compound {
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
Ok(nbt::binary::from_reader(r)?)
}
}
/// Wrapper type acting as a bridge between Serde and [Encode]/[Decode] through
/// the NBT format.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
pub struct NbtBridge<T>(pub T);
impl<T: Serialize> Encode for NbtBridge<T> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
Ok(nbt::binary::to_writer(w, &self.0)?)
}
}
impl<T: DeserializeOwned> Decode for NbtBridge<T> {
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
Ok(Self(nbt::binary::from_reader(r)?))
let (nbt, _) = nbt::from_binary_slice(r)?;
Ok(nbt)
}
}

View file

@ -20,8 +20,7 @@ use crate::block_pos::BlockPos;
use crate::ident::Ident;
use crate::nbt::Compound;
use crate::protocol::{
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, NbtBridge, RawBytes,
VarInt, VarLong,
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, RawBytes, VarInt, VarLong,
};
use crate::slot::Slot;
use crate::text::Text;

View file

@ -353,7 +353,7 @@ pub mod play {
ChunkDataAndUpdateLight {
chunk_x: i32,
chunk_z: i32,
heightmaps: NbtBridge<ChunkDataHeightmaps>,
heightmaps: Compound,
blocks_and_biomes: Vec<u8>,
block_entities: Vec<ChunkDataBlockEntity>,
trust_edges: bool,
@ -366,11 +366,11 @@ pub mod play {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChunkDataHeightmaps {
#[serde(rename = "MOTION_BLOCKING", with = "crate::nbt::long_array")]
pub motion_blocking: Vec<i64>,
}
// #[derive(Clone, Debug, Serialize, Deserialize)]
// pub struct ChunkDataHeightmaps {
// #[serde(rename = "MOTION_BLOCKING", with = "crate::nbt::long_array")]
// pub motion_blocking: Vec<i64>,
// }
def_struct! {
ChunkDataBlockEntity {
@ -389,7 +389,8 @@ pub mod play {
gamemode: GameMode,
previous_gamemode: GameMode,
dimension_names: Vec<Ident>,
registry_codec: NbtBridge<RegistryCodec>,
/// Contains information about dimensions, biomes, and chats.
registry_codec: Compound,
/// The name of the dimension type being spawned into.
dimension_type_name: Ident,
/// The name of the dimension being spawned into.
@ -412,156 +413,6 @@ pub mod play {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegistryCodec {
#[serde(rename = "minecraft:dimension_type")]
pub dimension_type_registry: DimensionTypeRegistry,
#[serde(rename = "minecraft:worldgen/biome")]
pub biome_registry: BiomeRegistry,
#[serde(rename = "minecraft:chat_type")]
pub chat_type_registry: ChatTypeRegistry,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DimensionTypeRegistry {
#[serde(rename = "type")]
pub kind: Ident,
pub value: Vec<DimensionTypeRegistryEntry>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DimensionTypeRegistryEntry {
pub name: Ident,
pub id: i32,
pub element: DimensionType,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DimensionType {
pub piglin_safe: bool,
pub has_raids: bool,
pub monster_spawn_light_level: i32,
pub monster_spawn_block_light_limit: i32,
pub natural: bool,
pub ambient_light: f32,
pub fixed_time: Option<i64>,
pub infiniburn: String, // TODO: tag type?
pub respawn_anchor_works: bool,
pub has_skylight: bool,
pub bed_works: bool,
pub effects: Ident,
pub min_y: i32,
pub height: i32,
pub logical_height: i32,
pub coordinate_scale: f64,
pub ultrawarm: bool,
pub has_ceiling: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeRegistry {
#[serde(rename = "type")]
pub kind: Ident,
pub value: Vec<Biome>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Biome {
pub name: Ident,
pub id: i32,
pub element: BiomeProperty,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeProperty {
pub precipitation: String,
pub depth: f32,
pub temperature: f32,
pub scale: f32,
pub downfall: f32,
pub category: String,
pub temperature_modifier: Option<String>,
pub effects: BiomeEffects,
pub particle: Option<BiomeParticle>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeEffects {
pub sky_color: i32,
pub water_fog_color: i32,
pub fog_color: i32,
pub water_color: i32,
pub foliage_color: Option<i32>,
pub grass_color: Option<i32>,
pub grass_color_modifier: Option<String>,
pub music: Option<BiomeMusic>,
pub ambient_sound: Option<Ident>,
pub additions_sound: Option<BiomeAdditionsSound>,
pub mood_sound: Option<BiomeMoodSound>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeMusic {
pub replace_current_music: bool,
pub sound: Ident,
pub max_delay: i32,
pub min_delay: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeAdditionsSound {
pub sound: Ident,
pub tick_chance: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeMoodSound {
pub sound: Ident,
pub tick_delay: i32,
pub offset: f64,
pub block_search_extent: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeParticle {
pub probability: f32,
pub options: BiomeParticleOptions,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BiomeParticleOptions {
#[serde(rename = "type")]
pub kind: Ident,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChatTypeRegistry {
#[serde(rename = "type")]
pub kind: Ident,
pub value: Vec<ChatTypeRegistryEntry>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChatTypeRegistryEntry {
pub name: Ident,
pub id: i32,
pub element: ChatType,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChatType {
pub chat: ChatTypeChat,
pub narration: ChatTypeNarration,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChatTypeChat {}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ChatTypeNarration {
pub priority: String,
}
def_enum! {
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
GameMode: u8 {

View file

@ -58,95 +58,3 @@ impl Decode for Slot {
})
}
}
#[cfg(test)]
mod tests {
use serde_nbt::Value;
use super::*;
#[test]
fn slot_with_nbt() {
let mut buf: Vec<u8> = Vec::new();
// Example nbt blob
// https://wiki.vg/Slot_Data
let mut nbt = Compound::new();
{
let mut enchant = Compound::new();
enchant.insert("id".to_string(), Value::Short(1));
enchant.insert("lvl".to_string(), Value::Short(1));
let enchant_list = vec![enchant];
nbt.insert(
"StoredEnchantments".to_string(),
Value::List(enchant_list.into()),
);
nbt.insert("Unbreakable".to_string(), Value::Int(1));
}
Slot::Present {
item_id: VarInt(1),
item_count: 1,
nbt: Some(nbt),
}
.encode(&mut buf)
.unwrap();
let mut slice = buf.as_slice();
let (item_id, item_count, nbt) = match Slot::decode(&mut slice).unwrap() {
Slot::Empty => {
panic!("Slot should be present")
}
Slot::Present {
item_id,
item_count,
nbt,
} => (item_id, item_count, nbt),
};
assert_eq!(1, item_id.0);
assert_eq!(1, item_count);
assert_eq!(&Value::Int(1), nbt.unwrap().get("Unbreakable").unwrap());
assert!(slice.is_empty());
}
#[test]
fn slot_no_nbt() {
let mut buf: Vec<u8> = Vec::new();
Slot::Present {
item_id: VarInt(1),
item_count: 1,
nbt: None,
}
.encode(&mut buf)
.unwrap();
let mut slice = buf.as_slice();
let nbt = match Slot::decode(&mut slice).unwrap() {
Slot::Empty => {
panic!("Slot should be present")
}
Slot::Present { nbt, .. } => nbt,
};
assert_eq!(None, nbt);
assert!(slice.is_empty());
}
#[test]
fn empty_slot() {
let mut buf: Vec<u8> = Vec::new();
Slot::Empty.encode(&mut buf).unwrap();
let mut slice = buf.as_slice();
if let Slot::Present { .. } = Slot::decode(&mut slice).unwrap() {
panic!("Slot should be empty")
};
assert!(slice.is_empty());
}
}