diff --git a/README.md b/README.md index 5221651..9ccbb2b 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ N/A = Not applicable | Registry | Loads | Loads tags | Synced to client | Tags synced to client | |--------------------------------------------------|-------|------------|------------------|-----------------------| | advancement | ❌ | ❌ | ❌ | ❌ | -| banner_pattern | ✅ | ❌ | ❌ | ❌ | +| banner_pattern | ✅ | ✅ | ❌ | ❌ | | chat_type | ✅ | ❌ | ❌ | ❌ | -| damage_type | ✅ | ❌ | ❌ | ❌ | +| damage_type | ✅ | ✅ | ❌ | ❌ | | dimension | ❌ | ❌ | ❌ | ❌ | -| dimension_type | ✅ | ❌ | ❌ | ❌ | +| dimension_type | ✅ | ✅ | ❌ | ❌ | | enchantment | ❌ | ❌ | ❌ | ❌ | | enchantment_provider | ❌ | ❌ | ❌ | ❌ | @@ -26,17 +26,17 @@ N/A = Not applicable | item_modifier | ❌ | ❌ | ❌ | ❌ | | jukebox_song | ❌ | ❌ | ❌ | ❌ | | loot_table | ❌ | ❌ | ❌ | ❌ | -| painting_variant | ✅ | ❌ | ❌ | ❌ | +| painting_variant | ✅ | ✅ | ❌ | ❌ | | predicate | ❌ | ❌ | ❌ | ❌ | | recipe | ❌ | ❌ | ❌ | ❌ | | trial_spawner | ❌ | ❌ | ❌ | ❌ | -| trim_material | ✅ | ❌ | ❌ | ❌ | -| trim_pattern | ✅ | ❌ | ❌ | ❌ | -| wolf_variant | ✅ | ❌ | ❌ | ❌ | -| worldgen/biome | ✅ | ❌ | ❌ | ❌ | +| trim_material | ✅ | ✅ | ❌ | ❌ | +| trim_pattern | ✅ | ✅ | ❌ | ❌ | +| wolf_variant | ✅ | ✅ | ❌ | ❌ | +| worldgen/biome | ✅ | ✅ | ❌ | ❌ | | worldgen/configured_carver | ❌ | ❌ | ❌ | ❌ | | worldgen/configured_feature | ❌ | ❌ | ❌ | ❌ | | worldgen/density_function | ❌ | ❌ | ❌ | ❌ | diff --git a/potato-data/src/datapack.rs b/potato-data/src/datapack.rs index d19f85e..83ffc58 100644 --- a/potato-data/src/datapack.rs +++ b/potato-data/src/datapack.rs @@ -1,10 +1,13 @@ use std::{ffi::OsStr, fs::read_to_string, path::PathBuf}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::{ + TagOrIdentifier, identifier::Identifier, - registry::{Registries, Registry}, + registry::{Registries, Registry, RegistryError}, + tag::Tag, }; pub struct Datapack { @@ -35,7 +38,7 @@ impl Datapack { Ok(()) } - fn load_registry( + fn load_registry( &self, registry: &mut Registry, path: &str, @@ -55,26 +58,85 @@ impl Datapack { .and_then(OsStr::to_str) .ok_or(DatapackError::Utf8)?; let path = namespace.join(path); - if let Ok(files) = std::fs::read_dir(path) { - for file in files { - let file = file?.path(); - let name = file - .file_stem() - .and_then(OsStr::to_str) - .ok_or(DatapackError::Utf8)?; - let identfiier = Identifier::new_str(namespace_str, name); + let files = std::fs::read_dir(path)?; + for file in files { + println!("Loading file: {:?}", file); + let file = file?.path(); + let name = file + .file_stem() + .and_then(OsStr::to_str) + .ok_or(DatapackError::Utf8)?; + let identfiier = Identifier::new_str(namespace_str, name); - let data: T = serde_json::from_str(&read_to_string(file)?)?; + let data: T = serde_json::from_str(&read_to_string(file)?)?; - registry.insert(identfiier, data); - } + registry.register(identfiier, data)?; } } + self.load_registry_tags(registry, path)?; + + Ok(()) + } + + fn load_registry_tags( + &self, + registry: &mut Registry, + path: &str, + ) -> Result<(), DatapackError> { + let namespaces: Vec<_> = self + .path + .join("data") + .read_dir() + .unwrap() + .map(|f| f.unwrap().path()) + .filter(|p| p.is_dir()) + .collect(); + + for namespace in namespaces { + let namespace_str = namespace + .file_name() + .and_then(OsStr::to_str) + .ok_or(DatapackError::Utf8)?; + let path = namespace.join("tags").join(path); + load_registry_tags_in_directory(registry, namespace_str, path)?; + } + Ok(()) } } +fn load_registry_tags_in_directory( + registry: &mut Registry, + namespace: &str, + dir: PathBuf, +) -> Result<(), DatapackError> { + if let Ok(files) = std::fs::read_dir(dir) { + for file in files { + let file = file?.path(); + if file.is_dir() { + load_registry_tags_in_directory(registry, namespace, file)?; + } else { + let name = file + .file_stem() + .and_then(OsStr::to_str) + .ok_or(DatapackError::Utf8)?; + let tag = Tag::new_str(namespace, name); + + let data: RegistryTagData = serde_json::from_str(&read_to_string(file)?)?; + + if data.replace { + registry.replace_tag(tag, data.values); + } else { + registry.register_tag(tag, data.values); + } + } + } + } + + Ok(()) +} + #[derive(Debug, Error)] pub enum DatapackError { #[error("IO error while reading datapack: {0}")] @@ -83,4 +145,13 @@ pub enum DatapackError { Json(#[from] serde_json::Error), #[error("Invalid UTF-8 in datapack")] Utf8, + #[error("Error while registering item in datapack: {0}")] + Registry(#[from] RegistryError), +} + +#[derive(Debug, Serialize, Deserialize)] +struct RegistryTagData { + #[serde(default)] + replace: bool, + values: Vec, } diff --git a/potato-data/src/identifier.rs b/potato-data/src/identifier.rs index 30db5b8..1542c41 100644 --- a/potato-data/src/identifier.rs +++ b/potato-data/src/identifier.rs @@ -1,3 +1,5 @@ +use std::fmt; + use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -42,6 +44,12 @@ impl Identifier { } } +impl fmt::Display for Identifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.namespace, self.path) + } +} + impl From for String { fn from(value: Identifier) -> Self { (&value).into() diff --git a/potato-data/src/lib.rs b/potato-data/src/lib.rs index 11df7f5..efc66bb 100644 --- a/potato-data/src/lib.rs +++ b/potato-data/src/lib.rs @@ -1,4 +1,6 @@ +use identifier::Identifier; use serde::{Deserialize, Serialize}; +use tag::Tag; pub mod block_state; pub mod color; @@ -23,3 +25,10 @@ pub enum OneOrMany { One(T), Many(Vec), } + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(untagged)] +pub enum TagOrIdentifier { + Tag(Tag), + Identifier(Identifier), +} diff --git a/potato-data/src/registry/mod.rs b/potato-data/src/registry/mod.rs index a04225a..04333a3 100644 --- a/potato-data/src/registry/mod.rs +++ b/potato-data/src/registry/mod.rs @@ -5,12 +5,13 @@ use chat_type::ChatType; use damage_type::DamageType; use dimension_type::DimensionType; use painting_variant::PaintingVariant; +use thiserror::Error; use trim_material::TrimMaterial; use trim_pattern::TrimPattern; use wolf_variant::WolfVariant; use worldgen::biome::Biome; -use crate::identifier::Identifier; +use crate::{TagOrIdentifier, identifier::Identifier, tag::Tag}; pub mod banner_pattern; pub mod chat_type; @@ -22,8 +23,6 @@ pub mod trim_pattern; pub mod wolf_variant; pub mod worldgen; -pub type Registry = HashMap; - #[derive(Default)] pub struct Registries { // advancement @@ -71,3 +70,89 @@ pub struct WorldgenRegistries { // flat_level_generator_preset // multi_noise_biome_source_parameter_list } + +#[derive(Debug, Error)] +pub enum RegistryError { + #[error("Duplicate key: {0}")] + DuplicateKey(Identifier), +} + +pub struct Registry +where + T: Clone, +{ + // Int id -> T + by_id: Vec, + // Int id -> Identifier + id_to_key: Vec, + // Identifier -> Int Id + to_id: HashMap, + // Identifier -> T + by_key: HashMap, + + tags: HashMap>, +} + +impl Default for Registry +where + T: Clone, +{ + fn default() -> Self { + Self { + by_id: Default::default(), + id_to_key: Default::default(), + to_id: Default::default(), + by_key: Default::default(), + tags: Default::default(), + } + } +} + +impl Registry +where + T: Clone, +{ + pub fn register(&mut self, key: Identifier, value: T) -> Result<(), RegistryError> { + if self.to_id.contains_key(&key) { + return Err(RegistryError::DuplicateKey(key)); + } + let id = self.by_id.len(); + self.by_id.push(value.clone()); + self.id_to_key.push(key.clone()); + self.to_id.insert(key.clone(), id); + self.by_key.insert(key, value); + + Ok(()) + } + + pub fn register_tag(&mut self, tag: Tag, entries: Vec) { + match self.tags.get_mut(&tag) { + Some(values) => { + values.extend(entries); + } + None => { + self.tags.insert(tag, entries); + } + } + } + + pub fn replace_tag(&mut self, tag: Tag, entries: Vec) { + self.tags.insert(tag, entries); + } + + pub fn len(&self) -> usize { + self.by_id.len() + } + + pub fn tags_len(&self) -> usize { + self.tags.len() + } + + pub fn is_empty(&self) -> bool { + self.by_id.is_empty() + } + + pub fn iter(&self) -> impl Iterator { + self.id_to_key.iter().zip(self.by_id.iter()) + } +} diff --git a/potato-data/src/registry/trim_material.rs b/potato-data/src/registry/trim_material.rs index 633d7e1..a401277 100644 --- a/potato-data/src/registry/trim_material.rs +++ b/potato-data/src/registry/trim_material.rs @@ -7,8 +7,9 @@ use crate::{identifier::Identifier, text_component::TextComponent}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TrimMaterial { pub asset_name: String, - pub description: TextComponent, pub ingredient: Identifier, + #[serde(default)] + pub item_model_index: f32, pub override_armor_assets: Option>, - // NOTE: Wiki says item_model_index exists as option, but can't find it in the vanilla pack + pub description: TextComponent, } diff --git a/potato-data/src/registry/trim_pattern.rs b/potato-data/src/registry/trim_pattern.rs index d2ab20c..3b454fe 100644 --- a/potato-data/src/registry/trim_pattern.rs +++ b/potato-data/src/registry/trim_pattern.rs @@ -5,8 +5,8 @@ use crate::{identifier::Identifier, text_component::TextComponent}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TrimPattern { pub asset_id: Identifier, - pub description: TextComponent, pub template_item: Identifier, + pub description: TextComponent, #[serde(default)] pub decal: bool, } diff --git a/potato-data/src/registry/worldgen/biome.rs b/potato-data/src/registry/worldgen/biome.rs index 6cd5924..29f3eb8 100644 --- a/potato-data/src/registry/worldgen/biome.rs +++ b/potato-data/src/registry/worldgen/biome.rs @@ -20,7 +20,7 @@ pub struct Biome { pub spawn_costs: HashMap, } -#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum BiomeTemperatureModifier { #[default] @@ -51,7 +51,7 @@ pub struct SoundEvent { pub range: Option, } -#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum BiomeGrassColorModifier { #[default] diff --git a/potato-data/src/tag.rs b/potato-data/src/tag.rs index c575804..eada051 100644 --- a/potato-data/src/tag.rs +++ b/potato-data/src/tag.rs @@ -4,10 +4,28 @@ use thiserror::Error; use crate::identifier::{Identifier, IdentifierError}; // TODO: Needs to serialize/deserialize as the identifier with a hash in front. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] #[serde(try_from = "&str", into = "String")] pub struct Tag(Identifier); +impl Tag { + pub fn new(namespace: String, path: String) -> Self { + Self(Identifier::new(namespace, path)) + } + + pub fn minecraft(path: String) -> Self { + Self::new("minecraft".to_string(), path) + } + + pub fn new_str(namespace: &str, path: &str) -> Self { + Self::new(namespace.to_string(), path.to_string()) + } + + pub fn minecraft_str(path: &str) -> Self { + Self::minecraft(path.to_string()) + } +} + impl TryFrom<&str> for Tag { type Error = TagError; fn try_from(value: &str) -> Result { diff --git a/potato-protocol/src/packet/clientbound/registry_data.rs b/potato-protocol/src/packet/clientbound/registry_data.rs index a300f92..af71476 100644 --- a/potato-protocol/src/packet/clientbound/registry_data.rs +++ b/potato-protocol/src/packet/clientbound/registry_data.rs @@ -1,10 +1,20 @@ use potato_data::{ + Either, identifier::Identifier, registry::{ - banner_pattern::BannerPattern, chat_type::ChatType, damage_type::DamageType, - dimension_type::DimensionType, painting_variant::PaintingVariant, - trim_material::TrimMaterial, trim_pattern::TrimPattern, wolf_variant::WolfVariant, - worldgen::biome::Biome, + self, + banner_pattern::BannerPattern, + chat_type::ChatType, + damage_type::DamageType, + dimension_type::DimensionType, + painting_variant::PaintingVariant, + trim_material::TrimMaterial, + trim_pattern::TrimPattern, + wolf_variant::WolfVariant, + worldgen::biome::{ + BiomeAdditionsSound, BiomeGrassColorModifier, BiomeMoodSound, BiomeMusicEntry, + BiomeParticle, BiomeTemperatureModifier, SoundEvent, + }, }, }; use potato_protocol_derive::{Packet, PacketEncodable}; @@ -55,10 +65,65 @@ impl_from_registry!( TrimMaterial, TrimPattern, BannerPattern, - Biome, ChatType, DamageType, DimensionType, WolfVariant, PaintingVariant ); + +#[derive(Debug, Serialize, Deserialize)] +pub struct Biome { + has_precipitation: bool, + temperature: f32, + temperature_modifier: BiomeTemperatureModifier, + downfall: f32, + effects: BiomeEffects, +} + +impl From<®istry::worldgen::biome::Biome> for RegistryData { + fn from(value: ®istry::worldgen::biome::Biome) -> Self { + RegistryData::Biome(Box::new(Biome { + has_precipitation: value.has_precipitation, + temperature: value.temperature, + temperature_modifier: value.temperature_modifier, + downfall: value.downfall, + effects: (&value.effects).into(), + })) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BiomeEffects { + fog_color: i32, + water_color: i32, + water_fog_color: i32, + sky_color: i32, + foliage_color: Option, + grass_color: Option, + grass_color_modifier: BiomeGrassColorModifier, + particle: Option, + ambient_sound: Option>, + mood_sound: Option, + additions_sound: Option, + music: Option>, +} + +impl From<®istry::worldgen::biome::BiomeEffects> for BiomeEffects { + fn from(value: ®istry::worldgen::biome::BiomeEffects) -> Self { + BiomeEffects { + fog_color: value.fog_color, + water_color: value.water_color, + water_fog_color: value.water_fog_color, + sky_color: value.sky_color, + foliage_color: value.foliage_color, + grass_color: value.grass_color, + grass_color_modifier: value.grass_color_modifier, + particle: value.particle.clone(), + ambient_sound: value.ambient_sound.clone(), + mood_sound: value.mood_sound.clone(), + additions_sound: value.additions_sound.clone(), + music: value.music.clone(), + } + } +} diff --git a/potato/src/connection.rs b/potato/src/connection.rs index f248e64..98d4224 100644 --- a/potato/src/connection.rs +++ b/potato/src/connection.rs @@ -437,7 +437,7 @@ impl Client { Ok(()) } - async fn send_registry( + async fn send_registry( &self, identifier: Identifier, registry: &Registry, diff --git a/potato/src/server.rs b/potato/src/server.rs index a104740..37bed33 100644 --- a/potato/src/server.rs +++ b/potato/src/server.rs @@ -6,6 +6,13 @@ pub struct Server { pub registries: Arc, } +macro_rules! print_registry_loaded { + ($registry:expr, $name:literal) => { + println!("Loaded {} {}s", $registry.len(), $name); + println!("Loaded {} {} tags", $registry.tags_len(), $name); + }; +} + impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Server { @@ -24,20 +31,15 @@ impl Server { .for_each(|pack| pack.load(&mut registries).expect("Failed to load datapack")); println!("Finished loading datapacks"); - println!("Loaded {} trim materials", registries.trim_materials.len()); - println!("Loaded {} trim patterns", registries.trim_patterns.len()); - println!("Loaded {} chat types", registries.chat_types.len()); - println!( - "Loaded {} dimension types", - registries.dimension_types.len() - ); - println!( - "Loaded {} painting variants", - registries.painting_variants.len() - ); - println!("Loaded {} wolf variants", registries.wolf_variants.len()); - println!("Loaded {} damage types", registries.damage_types.len()); - println!("Loaded {} biomes", registries.worldgen.biomes.len()); + print_registry_loaded!(registries.trim_materials, "trim material"); + print_registry_loaded!(registries.trim_patterns, "trim pattern"); + print_registry_loaded!(registries.banner_patterns, "banner pattern"); + print_registry_loaded!(registries.chat_types, "chat type"); + print_registry_loaded!(registries.dimension_types, "dimension type"); + print_registry_loaded!(registries.painting_variants, "painting variant"); + print_registry_loaded!(registries.wolf_variants, "wolf variant"); + print_registry_loaded!(registries.damage_types, "damage_type"); + print_registry_loaded!(registries.worldgen.biomes, "biome"); Server { registries: Arc::new(registries),