mirror of
https://github.com/italicsjenga/valence.git
synced 2025-02-23 18:17:44 +11:00
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:
parent
cc9cd0be2d
commit
36b63e777e
12 changed files with 222 additions and 431 deletions
|
@ -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"
|
||||
|
|
142
src/biome.rs
142
src/biome.rs
|
@ -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,
|
||||
|
|
11
src/chunk.rs
11
src/chunk.rs
|
@ -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,
|
||||
|
|
108
src/client.rs
108
src/client.rs
|
@ -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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use serde_nbt::Compound;
|
||||
use valence_nbt::Compound;
|
||||
use vek::Vec3;
|
||||
|
||||
use super::Client;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
src/ident.rs
30
src/ident.rs
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
92
src/slot.rs
92
src/slot.rs
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue