From 6ffad8ff20854494324601be2545ae5a83d31415 Mon Sep 17 00:00:00 2001 From: Kalle Struik Date: Fri, 7 Mar 2025 22:41:39 +0100 Subject: [PATCH] Load a bunch of registries from datapacks, logging in is broken by this for now --- Cargo.lock | 13 +- Cargo.toml | 2 +- potato-data/Cargo.toml | 9 + potato-data/src/block_state.rs | 11 + potato-data/src/color.rs | 35 +++ potato-data/src/datapack.rs | 86 ++++++ potato-data/src/identifier.rs | 71 +++++ potato-data/src/item_stack.rs | 12 + potato-data/src/lib.rs | 25 ++ potato-data/src/particle.rs | 268 ++++++++++++++++++ potato-data/src/registry/banner_pattern.rs | 9 + potato-data/src/registry/chat_type.rs | 24 ++ potato-data/src/registry/damage_type.rs | 37 +++ potato-data/src/registry/dimension_type.rs | 74 +++++ potato-data/src/registry/mod.rs | 49 ++++ potato-data/src/registry/painting_variant.rs | 9 + potato-data/src/registry/trim_material.rs | 14 + potato-data/src/registry/trim_pattern.rs | 12 + potato-data/src/registry/wolf_variant.rs | 11 + potato-data/src/registry/worldgen/biome.rs | 124 ++++++++ potato-data/src/registry/worldgen/mod.rs | 1 + potato-data/src/tag.rs | 34 +++ potato-data/src/text_component.rs | 166 +++++++++++ potato-protocol/Cargo.toml | 5 +- potato-protocol/src/datatypes/identifier.rs | 79 ------ potato-protocol/src/datatypes/mod.rs | 1 - .../src/packet/clientbound/login.rs | 3 +- .../src/packet/clientbound/registry_data.rs | 145 +++------- potato-protocol/src/packet_encodable/mod.rs | 24 ++ potato/Cargo.toml | 1 + potato/src/connection.rs | 142 +++------- potato/src/datapack/damage_type.rs | 86 ------ potato/src/datapack/mod.rs | 56 ---- potato/src/main.rs | 1 - potato/src/server.rs | 37 ++- 35 files changed, 1230 insertions(+), 446 deletions(-) create mode 100644 potato-data/Cargo.toml create mode 100644 potato-data/src/block_state.rs create mode 100644 potato-data/src/color.rs create mode 100644 potato-data/src/datapack.rs create mode 100644 potato-data/src/identifier.rs create mode 100644 potato-data/src/item_stack.rs create mode 100644 potato-data/src/lib.rs create mode 100644 potato-data/src/particle.rs create mode 100644 potato-data/src/registry/banner_pattern.rs create mode 100644 potato-data/src/registry/chat_type.rs create mode 100644 potato-data/src/registry/damage_type.rs create mode 100644 potato-data/src/registry/dimension_type.rs create mode 100644 potato-data/src/registry/mod.rs create mode 100644 potato-data/src/registry/painting_variant.rs create mode 100644 potato-data/src/registry/trim_material.rs create mode 100644 potato-data/src/registry/trim_pattern.rs create mode 100644 potato-data/src/registry/wolf_variant.rs create mode 100644 potato-data/src/registry/worldgen/biome.rs create mode 100644 potato-data/src/registry/worldgen/mod.rs create mode 100644 potato-data/src/tag.rs create mode 100644 potato-data/src/text_component.rs delete mode 100644 potato-protocol/src/datatypes/identifier.rs delete mode 100644 potato/src/datapack/damage_type.rs delete mode 100644 potato/src/datapack/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3d72f95..2123afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,7 +106,7 @@ dependencies = [ [[package]] name = "fastnbt" version = "2.5.0" -source = "git+https://github.com/owengage/fastnbt.git#e2a5d8a7001d4f074ae99fd21bb485667934baeb" +source = "git+https://github.com/CheAle14/fastnbt.git#91ce891ca8aa3048b9e1d08ae0fcc51a7b714451" dependencies = [ "byteorder", "cesu8", @@ -223,6 +223,7 @@ name = "potato" version = "0.1.0" dependencies = [ "bytes", + "potato-data", "potato-protocol", "serde", "serde_json", @@ -231,6 +232,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "potato-data" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "potato-protocol" version = "0.1.0" @@ -238,6 +248,7 @@ dependencies = [ "byteorder", "bytes", "fastnbt", + "potato-data", "potato-protocol-derive", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index d011cee..484f6df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["potato", "potato-protocol", "potato-protocol-derive"] +members = ["potato", "potato-data", "potato-protocol", "potato-protocol-derive"] [workspace.package] version = "0.1.0" diff --git a/potato-data/Cargo.toml b/potato-data/Cargo.toml new file mode 100644 index 0000000..6351032 --- /dev/null +++ b/potato-data/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "potato-data" +version.workspace = true +edition.workspace = true + +[dependencies] +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true diff --git a/potato-data/src/block_state.rs b/potato-data/src/block_state.rs new file mode 100644 index 0000000..1c1a57c --- /dev/null +++ b/potato-data/src/block_state.rs @@ -0,0 +1,11 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::identifier::Identifier; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BlockState { + pub name: Identifier, + pub properties: HashMap, +} diff --git a/potato-data/src/color.rs b/potato-data/src/color.rs new file mode 100644 index 0000000..dad2d89 --- /dev/null +++ b/potato-data/src/color.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +// TODO: Serialize as array of [red, green, blue] in NBT +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct RGBf32 { + red: f32, + green: f32, + blue: f32, +} + +// TODO: Serialize as array of [red, green, blue, alpha] in NBT +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct RGBAf32 { + red: f32, + green: f32, + blue: f32, + alpha: f32, +} + +// TODO: Serialize as u32 of red<<16+green<<8+blue in NBT +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct RGBu8 { + red: u8, + green: u8, + blue: u8, +} + +// TODO: Serialize as u32 of alpha<<24+red<<16+green<<8+blue in NBT +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ARGBu8 { + alpha: u8, + red: u8, + green: u8, + blue: u8, +} diff --git a/potato-data/src/datapack.rs b/potato-data/src/datapack.rs new file mode 100644 index 0000000..a652ad1 --- /dev/null +++ b/potato-data/src/datapack.rs @@ -0,0 +1,86 @@ +use std::{ffi::OsStr, fs::read_to_string, path::PathBuf}; + +use thiserror::Error; + +use crate::{ + identifier::Identifier, + registry::{Registries, Registry}, +}; + +pub struct Datapack { + pub name: String, + path: PathBuf, +} + +impl Datapack { + pub fn new(path: PathBuf) -> Self { + // TODO: Load name (and other info) from pack.mcmeta + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + + Self { name, path } + } + + pub fn load(&self, registries: &mut Registries) -> Result<(), DatapackError> { + println!("Loading datapack: {}", self.name); + self.load_registry(&mut registries.trim_materials, "trim_material")?; + self.load_registry(&mut registries.trim_patterns, "trim_pattern")?; + self.load_registry(&mut registries.banner_patterns, "banner_pattern")?; + self.load_registry(&mut registries.worldgen.biomes, "worldgen/biome")?; + self.load_registry(&mut registries.damage_types, "damage_type")?; + self.load_registry(&mut registries.dimension_types, "dimension_type")?; + self.load_registry(&mut registries.wolf_variants, "wolf_variant")?; + self.load_registry(&mut registries.painting_variants, "painting_variant")?; + self.load_registry(&mut registries.chat_types, "chat_type")?; + + Ok(()) + } + + fn load_registry( + &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(path); + if let Ok(files) = std::fs::read_dir(path) { + for file in files { + let file = file.unwrap().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)?)?; + + registry.insert(identfiier, data); + } + } + } + + Ok(()) + } +} + +#[derive(Debug, Error)] +pub enum DatapackError { + #[error("IO error while reading datapack: {0}")] + Io(#[from] std::io::Error), + #[error("Error while parsing JSON in datapack: {0}")] + Json(#[from] serde_json::Error), + #[error("Invalid UTF-8 in datapack")] + Utf8, +} diff --git a/potato-data/src/identifier.rs b/potato-data/src/identifier.rs new file mode 100644 index 0000000..30db5b8 --- /dev/null +++ b/potato-data/src/identifier.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +// TODO: Custom serializer/deserializer for format namespace:path as string +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(try_from = "&str", into = "String")] +pub struct Identifier { + pub namespace: String, + pub path: String, +} + +impl Identifier { + pub fn new(namespace: String, path: String) -> Self { + // TODO: Validate namespace and path + Self { 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()) + } + + pub fn from_raw_str(raw: &str) -> Result { + let mut parts = raw.split(":"); + Ok(Self::new( + parts + .next() + .ok_or(IdentifierError::InvalidNamespace)? + .to_string(), + parts + .next() + .ok_or(IdentifierError::InvalidPath)? + .to_string(), + )) + } +} + +impl From for String { + fn from(value: Identifier) -> Self { + (&value).into() + } +} + +impl From<&Identifier> for String { + fn from(value: &Identifier) -> Self { + format!("{}:{}", value.namespace, value.path) + } +} + +impl TryFrom<&str> for Identifier { + type Error = IdentifierError; + + fn try_from(value: &str) -> Result { + Identifier::from_raw_str(value) + } +} + +#[derive(Debug, Error)] +pub enum IdentifierError { + #[error("Invalid namespace")] + InvalidNamespace, + #[error("Invalid path")] + InvalidPath, +} diff --git a/potato-data/src/item_stack.rs b/potato-data/src/item_stack.rs new file mode 100644 index 0000000..a19e054 --- /dev/null +++ b/potato-data/src/item_stack.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +use crate::identifier::Identifier; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ItemStack { + id: Identifier, + components: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub enum ItemComponent {} diff --git a/potato-data/src/lib.rs b/potato-data/src/lib.rs new file mode 100644 index 0000000..11df7f5 --- /dev/null +++ b/potato-data/src/lib.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +pub mod block_state; +pub mod color; +pub mod datapack; +pub mod identifier; +pub mod item_stack; +pub mod particle; +pub mod registry; +pub mod tag; +pub mod text_component; + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(untagged)] +pub enum Either { + Left(A), + Right(B), +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(untagged)] +pub enum OneOrMany { + One(T), + Many(Vec), +} diff --git a/potato-data/src/particle.rs b/potato-data/src/particle.rs new file mode 100644 index 0000000..0155ba7 --- /dev/null +++ b/potato-data/src/particle.rs @@ -0,0 +1,268 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + Either, + block_state::BlockState, + color::{ARGBu8, RGBAf32, RGBf32, RGBu8}, + identifier::Identifier, + item_stack::ItemStack, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "type")] +pub enum Particle { + #[serde(rename = "minecraft:angry_villager")] + AngryVillager, + #[serde(rename = "minecraft:ash")] + Ash, + Block { + block_state: Either, + }, + #[serde(rename = "minecraft:block_crumble")] + BlockCrumble { + block_state: Either, + }, + BlockMarker { + block_state: Either, + }, + #[serde(rename = "minecraft:bubble")] + Bubble, + #[serde(rename = "minecraft:bubble_column_up")] + BubbleColumnUp, + #[serde(rename = "minecraft:bubble_pop")] + BuublePop, + #[serde(rename = "minecraft:campfire_cosy_smoke")] + CampfireCosySmoke, + #[serde(rename = "minecraft:campfire_signal_smoke")] + CampfireSignalSmoke, + #[serde(rename = "minecraft:cherry_leaves")] + CherryLeaves, + #[serde(rename = "minecraft:cloud")] + Cloud, + #[serde(rename = "minecraft:composter")] + Composter, + #[serde(rename = "minecraft:crimson_spore")] + CrimsonSpore, + #[serde(rename = "minecraft:crit")] + Crit, + #[serde(rename = "minecraft:current_down")] + CurrentDown, + #[serde(rename = "minecraft:damage_indicator")] + DamageIndicator, + #[serde(rename = "minecraft:dolphin")] + Dolphin, + #[serde(rename = "minecraft:dragon_breath")] + DragonBreath, + #[serde(rename = "minecraft:dripping_dripstone_lava")] + DrippingDripstoneLava, + #[serde(rename = "minecraft:dripping_dripstone_water")] + DrippingDripstoneWater, + #[serde(rename = "minecraft:dripping_honey")] + DrippingHoney, + #[serde(rename = "minecraft:dripping_lava")] + DrippingLava, + #[serde(rename = "minecraft:dripping_obsidian_tear")] + DrippingObsidianTear, + #[serde(rename = "minecraft:dripping_water")] + DrippingWater, + Dust { + color: RGBf32, + scale: f32, + }, + DustColorTransition { + from_color: RGBf32, + to_color: RGBf32, + scale: f32, + }, + DustPillar { + block_state: Either, + }, + #[serde(rename = "minecraft:dust_plume")] + DustPlume, + #[serde(rename = "minecraft:effect")] + Effect, + #[serde(rename = "minecraft:egg_crack")] + EggCrack, + #[serde(rename = "minecraft:elder_guardian")] + ElderGuardian, + #[serde(rename = "minecraft:electric_spark")] + ElectricSpark, + #[serde(rename = "minecraft:enchant")] + Enchant, + #[serde(rename = "minecraft:enchanted_hit")] + EnchantedHit, + #[serde(rename = "minecraft:end_rod")] + EndRod, + EntityEffect { + color: Either, + }, + #[serde(rename = "minecraft:explosion")] + Explosion, + #[serde(rename = "minecraft:explosion_emitter")] + ExplosionEmitter, + #[serde(rename = "minecraft:falling_dripstone_lava")] + FallingDripstoneLava, + #[serde(rename = "minecraft:falling_dripstone_water")] + FallingDrpistoneWater, + FallingDust { + block_state: Either, + }, + #[serde(rename = "minecraft:falling_honey")] + FallingHoney, + #[serde(rename = "minecraft:falling_lava")] + FallingLava, + #[serde(rename = "minecraft:falling_nectar")] + FallingNectar, + #[serde(rename = "minecraft:falling_obsidian_tear")] + FallingObsidianTear, + #[serde(rename = "minecraft:falling_spore_blossom")] + FallingSporeBlossom, + #[serde(rename = "minecraft:falling_water")] + FallingWater, + // Firefly, (Upcoming: 1.21.5) + #[serde(rename = "minecraft:firework")] + Firework, + #[serde(rename = "minecraft:fishing")] + Fishing, + #[serde(rename = "minecraft:flame")] + Flame, + #[serde(rename = "minecraft:flash")] + Flash, + #[serde(rename = "minecraft:glow")] + Glow, + #[serde(rename = "minecraft:glow_squid_ink")] + GlowSquidInk, + #[serde(rename = "minecraft:gust")] + Gust, + #[serde(rename = "minecraft:gust_emitter")] + GustEmitter, + #[serde(rename = "minecraft:happy_villager")] + HappyVillager, + #[serde(rename = "minecraft:heart")] + Heart, + #[serde(rename = "minecraft:infested")] + Infested, + #[serde(rename = "minecraft:instant_effect")] + InstantEffect, + Item { + item: Either, + }, + #[serde(rename = "minecraft:item_cobweb")] + ItemCobweb, + #[serde(rename = "minecraft:item_slime")] + ItemSlime, + #[serde(rename = "minecraft:item_snowball")] + ItemSnowball, + #[serde(rename = "minecraft:landing_honey")] + LandingHoney, + #[serde(rename = "minecraft:landing_lava")] + LandingLava, + #[serde(rename = "minecraft:landing_obsidian_tear")] + LandingObsidianTear, + #[serde(rename = "minecraft:large_smoke")] + LargeSmoke, + #[serde(rename = "minecraft:lava")] + Lava, + #[serde(rename = "minecraft:mycelium")] + Mycelium, + #[serde(rename = "minecraft:nautilus")] + Nautlius, + #[serde(rename = "minecraft:note")] + Note, + #[serde(rename = "minecraft:ominous_spawning")] + OminousSpawning, + #[serde(rename = "minecraft:pale_oak_leaves")] + PaleOakLeaves, + #[serde(rename = "minecraft:poof")] + Poof, + #[serde(rename = "minecraft:portal")] + Portal, + #[serde(rename = "minecraft:raid_omen")] + RaidOmen, + #[serde(rename = "minecraft:rain")] + Rain, + #[serde(rename = "minecraft:reverse_portal")] + ReversePortal, + #[serde(rename = "minecraft:scrape")] + Scrape, + SculkCharge { + roll: f32, + }, + #[serde(rename = "minecraft:sculk_charge_pop")] + SculkChargePop, + #[serde(rename = "minecraft:sculk_soul")] + SculkSoul, + Shriek { + delay: i32, + }, + #[serde(rename = "minecraft:small_flame")] + SmallFlame, + #[serde(rename = "minecraft:small_gust")] + SmallGust, + #[serde(rename = "minecraft:smoke")] + Smoke, + #[serde(rename = "minecraft:sneeze")] + Sneeze, + #[serde(rename = "minecraft:snowflake")] + Snowflake, + #[serde(rename = "minecraft:sonic_boom")] + SonicBoom, + #[serde(rename = "minecraft:soul")] + Soul, + #[serde(rename = "minecraft:soul_fire_flame")] + SoulFireFlame, + #[serde(rename = "minecraft:spit")] + Spit, + #[serde(rename = "minecraft:splash")] + Splash, + #[serde(rename = "minecraft:spore_blossom_air")] + SporeBlossomAir, + #[serde(rename = "minecraft:squid_ink")] + SquidInk, + #[serde(rename = "minecraft:sweep_attack")] + SweepAttack, + #[serde(rename = "minecraft:totem_of_undying")] + TotemOfUndying, + Trail { + target: (f32, f32, f32), + colr: RGBu8, + }, + #[serde(rename = "minecraft:trail_omen")] + TrailOmen, + #[serde(rename = "minecraft:trail_spawner_detection")] + TrailSpawnerDetection, + #[serde(rename = "minecraft:underwater")] + Underwater, + #[serde(rename = "minecraft:vault_connection")] + VaultConnection, + Vibration { + destination: PositionSource, + arrival_in_ticks: i32, + }, + #[serde(rename = "minecraft:warped_spore")] + WarpedSpore, + #[serde(rename = "minecraft:wax_off")] + WaxOff, + #[serde(rename = "minecraft:wax_on")] + WaxOn, + #[serde(rename = "minecraft:white_ash")] + WhiteAsh, + #[serde(rename = "minecraft:white_smoke")] + WhiteSmoke, + #[serde(rename = "minecraft:witch")] + Witch, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PositionSource { + Block { + pos: (i32, i32, i32), + }, + Entity { + // source_entity stores a UUID as 4xi32 + source_entity: (i32, i32, i32, i32), + // y_offset is optional and defaults to 0.0 + y_offset: f32, + }, +} diff --git a/potato-data/src/registry/banner_pattern.rs b/potato-data/src/registry/banner_pattern.rs new file mode 100644 index 0000000..1d05e33 --- /dev/null +++ b/potato-data/src/registry/banner_pattern.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +use crate::identifier::Identifier; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BannerPattern { + pub asset_id: Identifier, + pub translation_key: String, +} diff --git a/potato-data/src/registry/chat_type.rs b/potato-data/src/registry/chat_type.rs new file mode 100644 index 0000000..bc48e21 --- /dev/null +++ b/potato-data/src/registry/chat_type.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +use crate::text_component::TextComponentStyle; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct ChatType { + pub chat: ChatTypeInner, + pub narration: ChatTypeInner, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ChatTypeParameter { + Sender, + Target, + Content, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct ChatTypeInner { + pub translation_key: String, + pub parameters: Vec, + pub style: Option, +} diff --git a/potato-data/src/registry/damage_type.rs b/potato-data/src/registry/damage_type.rs new file mode 100644 index 0000000..e5ecb04 --- /dev/null +++ b/potato-data/src/registry/damage_type.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DamageType { + message_id: String, + scaling: DamageTypeScaling, + exhaustion: f32, + effects: Option, + death_message_type: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeScaling { + Never, + WhenCausedByLivingNonPlayer, + Always, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeEffect { + Hurt, + Thorns, + Drowning, + Burning, + Poking, + Freezing, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeDeathMessageType { + Default, + FallVariants, + IntentionalGameDesign, +} diff --git a/potato-data/src/registry/dimension_type.rs b/potato-data/src/registry/dimension_type.rs new file mode 100644 index 0000000..8cce8f0 --- /dev/null +++ b/potato-data/src/registry/dimension_type.rs @@ -0,0 +1,74 @@ +use crate::Either; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DimensionType { + pub fixed_time: Option, + pub has_skylight: bool, + pub has_ceiling: bool, + pub ultrawarm: bool, + pub natural: bool, + pub coordinate_scale: f64, + pub bed_works: bool, + pub respawn_anchor_works: bool, + pub min_y: i32, + pub height: i32, + pub logical_height: i32, + pub infiniburn: String, // TODO: This is a tag, create a type for these + pub effects: DimensionEffect, + pub ambient_light: f32, + pub piglin_safe: bool, + pub has_raids: bool, + pub monster_spawn_light_level: Either, + pub monster_spawn_block_light_limit: i32, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum DimensionEffect { + #[serde(rename = "minecraft:overworld")] + Overworld, + #[serde(rename = "minecraft:the_nether")] + TheNether, + #[serde(rename = "minecraft:the_end")] + TheEnd, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "type")] +pub enum IntProvider { + #[serde(rename = "minecraft:constant")] + Constant { value: i32 }, + #[serde(rename = "minecraft:uniform")] + Uniform { + min_inclusive: i32, + max_inclusive: i32, + }, + #[serde(rename = "minecraft:biased_to_bottom")] + BiasedToBottom { + min_inclusive: i32, + max_inclusive: i32, + }, + #[serde(rename = "minecraft:clamped")] + Clamped { + min_inclusive: i32, + max_inclusive: i32, + source: Box, + }, + #[serde(rename = "minecraft:clamped_normal")] + ClampedNormal { + mean: f32, + deviation: f32, + min_include: i32, + max_include: i32, + }, + #[serde(rename = "minecraft:weighted_list")] + WeightedList { + distribution: Vec, + }, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WeightedListEntry { + pub data: Box, + pub weight: i32, +} diff --git a/potato-data/src/registry/mod.rs b/potato-data/src/registry/mod.rs new file mode 100644 index 0000000..7564258 --- /dev/null +++ b/potato-data/src/registry/mod.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use banner_pattern::BannerPattern; +use chat_type::ChatType; +use damage_type::DamageType; +use dimension_type::DimensionType; +use painting_variant::PaintingVariant; +use trim_material::TrimMaterial; +use trim_pattern::TrimPattern; +use wolf_variant::WolfVariant; +use worldgen::biome::Biome; + +use crate::identifier::Identifier; + +pub mod banner_pattern; +pub mod chat_type; +pub mod damage_type; +pub mod dimension_type; +pub mod painting_variant; +pub mod trim_material; +pub mod trim_pattern; +pub mod wolf_variant; +pub mod worldgen; + +pub type Registry = HashMap; + +#[derive(Default)] +pub struct Registries { + pub trim_materials: Registry, + pub trim_patterns: Registry, + pub banner_patterns: Registry, + pub worldgen: WorldgenRegistries, + pub dimension_types: Registry, + pub damage_types: Registry, + pub painting_variants: Registry, + pub wolf_variants: Registry, + pub chat_types: Registry, +} + +impl Registries { + pub fn empty() -> Registries { + Self::default() + } +} + +#[derive(Default)] +pub struct WorldgenRegistries { + pub biomes: Registry, +} diff --git a/potato-data/src/registry/painting_variant.rs b/potato-data/src/registry/painting_variant.rs new file mode 100644 index 0000000..949f737 --- /dev/null +++ b/potato-data/src/registry/painting_variant.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PaintingVariant { + pub asset_id: String, + pub height: i32, + pub width: i32, + // TODO: Missing title and author fields +} diff --git a/potato-data/src/registry/trim_material.rs b/potato-data/src/registry/trim_material.rs new file mode 100644 index 0000000..633d7e1 --- /dev/null +++ b/potato-data/src/registry/trim_material.rs @@ -0,0 +1,14 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +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, + pub override_armor_assets: Option>, + // NOTE: Wiki says item_model_index exists as option, but can't find it in the vanilla pack +} diff --git a/potato-data/src/registry/trim_pattern.rs b/potato-data/src/registry/trim_pattern.rs new file mode 100644 index 0000000..d2ab20c --- /dev/null +++ b/potato-data/src/registry/trim_pattern.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +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, + #[serde(default)] + pub decal: bool, +} diff --git a/potato-data/src/registry/wolf_variant.rs b/potato-data/src/registry/wolf_variant.rs new file mode 100644 index 0000000..5fa3819 --- /dev/null +++ b/potato-data/src/registry/wolf_variant.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +use crate::{Either, OneOrMany, identifier::Identifier, tag::Tag}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WolfVariant { + pub wild_texture: String, + pub tame_texture: String, + pub angry_texture: String, + pub biomes: OneOrMany>, +} diff --git a/potato-data/src/registry/worldgen/biome.rs b/potato-data/src/registry/worldgen/biome.rs new file mode 100644 index 0000000..6cd5924 --- /dev/null +++ b/potato-data/src/registry/worldgen/biome.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::{Either, OneOrMany, identifier::Identifier, particle::Particle, tag::Tag}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Biome { + pub has_precipitation: bool, + pub temperature: f32, + #[serde(default)] + pub temperature_modifier: BiomeTemperatureModifier, + pub downfall: f32, + pub effects: BiomeEffects, + pub carvers: OneOrMany>, + pub features: Vec>>, + // TODO: Limit between 0.0 and 0.9999999 + pub creature_spawn_probability: Option, + pub spawners: BiomeSpawners, + pub spawn_costs: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "snake_case")] +pub enum BiomeTemperatureModifier { + #[default] + None, + Frozen, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeEffects { + pub fog_color: i32, + pub sky_color: i32, + pub water_color: i32, + pub water_fog_color: i32, + pub foliage_color: Option, + pub grass_color: Option, + #[serde(default)] + pub grass_color_modifier: BiomeGrassColorModifier, + pub particle: Option, + pub ambient_sound: Option>, + pub mood_sound: Option, + pub additions_sound: Option, + pub music: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SoundEvent { + pub sound_id: Identifier, + pub range: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "snake_case")] +pub enum BiomeGrassColorModifier { + #[default] + None, + DarkForest, + Swamp, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeParticle { + pub probability: f64, + pub options: Particle, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeMoodSound { + pub sound: Either, + pub tick_delay: i32, + pub block_search_extent: i32, + pub offset: f64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeAdditionsSound { + pub sound: Either, + pub tick_chance: f64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeMusicEntry { + pub data: BiomeMusicData, + pub weight: i32, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeMusicData { + pub sound: Either, + pub min_delay: i32, + pub max_delay: i32, + pub replace_current_music: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeSpawners { + pub monster: Option>, + pub creature: Option>, + pub ambient: Option>, + pub water_creature: Option>, + pub underground_water_creature: Option>, + pub water_ambient: Option>, + pub misc: Option>, + pub axolotls: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeSpawner { + #[serde(rename = "type")] + pub ty: Identifier, + pub weight: i32, + #[serde(rename = "minCount")] + pub min_count: i32, + #[serde(rename = "maxCount")] + pub max_count: i32, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BiomeSpawnCost { + pub energy_budget: f64, + pub charge: f64, +} diff --git a/potato-data/src/registry/worldgen/mod.rs b/potato-data/src/registry/worldgen/mod.rs new file mode 100644 index 0000000..f1af41c --- /dev/null +++ b/potato-data/src/registry/worldgen/mod.rs @@ -0,0 +1 @@ +pub mod biome; diff --git a/potato-data/src/tag.rs b/potato-data/src/tag.rs new file mode 100644 index 0000000..c575804 --- /dev/null +++ b/potato-data/src/tag.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +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)] +#[serde(try_from = "&str", into = "String")] +pub struct Tag(Identifier); + +impl TryFrom<&str> for Tag { + type Error = TagError; + fn try_from(value: &str) -> Result { + match value.strip_prefix("#") { + Some(v) => Ok(Self(Identifier::try_from(v)?)), + None => Err(TagError::MissingHash), + } + } +} + +impl From for String { + fn from(value: Tag) -> Self { + let v: String = value.0.into(); + format!("#{v}") + } +} + +#[derive(Debug, Error)] +pub enum TagError { + #[error("Missing hash")] + MissingHash, + #[error("Invalid identifier: {0}")] + InvalidIdentifier(#[from] IdentifierError), +} diff --git a/potato-data/src/text_component.rs b/potato-data/src/text_component.rs new file mode 100644 index 0000000..38794bc --- /dev/null +++ b/potato-data/src/text_component.rs @@ -0,0 +1,166 @@ +use serde::{Deserialize, Serialize}; + +use crate::{Either, identifier::Identifier, item_stack::ItemComponent}; + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct TextComponent { + // Content + #[serde(flatten)] + pub content: TextComponentContent, + // Children + #[serde(default)] + pub extra: Vec, + // Formatting + #[serde(flatten)] + pub style: TextComponentStyle, + // Interactivity + pub insertion: Option, + #[serde(rename = "clickEvent")] + pub click_event: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct TextComponentStyle { + pub color: Option, + pub font: Option, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "action", content = "value", rename_all = "snake_case")] +pub enum ClickEvent { + OpenUrl(String), + OpenFile(String), + RunCommand(String), + SuggestCommand(String), + ChangePage(String), + CopyToClipboard(String), +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "action", content = "contents", rename_all = "snake_case")] +pub enum HoverEvent { + ShowText(Box), + ShowItem { + id: Identifier, + count: Option, + #[serde(default)] + components: Vec, + }, + ShowEntity { + // NOTE: Not sure if this is a TextComponent or just a string. + name: String, + #[serde(rename = "type")] + ty: Identifier, + id: Either, + }, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(untagged)] +pub enum TextComponentContent { + Text(TextComponentText), + Translatable(TextComponentTranslatable), + Score(TextComponentScore), + Selector(TextComponentSelector), + Keybind(TextComponentKeybind), + Nbt(TextComponentNbt), +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "text")] +pub struct TextComponentText { + pub text: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "translatable")] +pub struct TextComponentTranslatable { + pub translate: String, + pub fallback: Option, + pub with: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "score")] +pub struct TextComponentScore { + pub score: TextComponentScoreInner, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct TextComponentScoreInner { + pub name: String, + pub objective: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "selector")] +pub struct TextComponentSelector { + pub selector: String, + // TODO: Instead of an option, make this default to the default value. + pub separator: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "keybind")] +pub struct TextComponentKeybind { + pub keybind: String, // TODO: Maybe restrict this to the allowed keybinds, or maybe leave that + // for the client. +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(tag = "type", rename = "nbt")] +pub struct TextComponentNbt { + pub source: String, + pub nbt: String, + #[serde(default)] + pub interpret: bool, + // TODO: Instead of an option, make this default to the default value. + pub separator: Option>, + pub block: Option, + pub entity: Option, + pub storage: Option, +} + +#[cfg(test)] +mod test { + use crate::text_component::*; + + #[test] + fn test_text_component() { + let text = r#"{"text":"Hello, world!"}"#; + let text_component: TextComponent = serde_json::from_str(text).unwrap(); + assert_eq!( + text_component, + TextComponent { + content: TextComponentContent::Text(TextComponentText { + text: "Hello, world!".to_string(), + }), + } + ); + } + + #[test] + fn test_translatable_text_component() { + let text = r#"{"translate":"chat.type.text","with":[{"text":"Hello, world!"}]}"#; + let text_component: TextComponent = serde_json::from_str(text).unwrap(); + assert_eq!( + text_component, + TextComponent { + content: TextComponentContent::Translatable(TextComponentTranslatable { + translate: "chat.type.text".to_string(), + fallback: None, + with: Some(vec![TextComponent { + content: TextComponentContent::Text(TextComponentText { + text: "Hello, world!".to_string(), + }), + }]), + }), + } + ); + } +} diff --git a/potato-protocol/Cargo.toml b/potato-protocol/Cargo.toml index 266e431..e6c7728 100644 --- a/potato-protocol/Cargo.toml +++ b/potato-protocol/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +potato-data = { path = "../potato-data" } potato-protocol-derive = { path = "../potato-protocol-derive" } serde.workspace = true @@ -16,7 +17,9 @@ tokio.workspace = true byteorder = "1.5.0" # Build from git, since there has not been a release in over a year -fastnbt = { git = "https://github.com/owengage/fastnbt.git" } +# Original repo: https://github.com/owengage/fastnbt.git Using a fork +# to fix an issue with boolean serialization. +fastnbt = { git = "https://github.com/CheAle14/fastnbt.git" } [build-dependencies] serde.workspace = true diff --git a/potato-protocol/src/datatypes/identifier.rs b/potato-protocol/src/datatypes/identifier.rs deleted file mode 100644 index afcba94..0000000 --- a/potato-protocol/src/datatypes/identifier.rs +++ /dev/null @@ -1,79 +0,0 @@ -use bytes::{Buf, BufMut}; - -use crate::packet_encodable::{PacketDecodeError, PacketEncodable, PacketEncodeError}; - -// The _phantom is used to stop construction of the struct outside of the new() function. Not for -// future api compatibility, but to avoid accidental instantiation of invalid identifiers. -#[allow(clippy::manual_non_exhaustive)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Identifier { - pub namespace: String, - pub path: String, - _phantom: (), -} - -impl Identifier { - pub fn new(namespace: String, path: String) -> Self { - // TODO: Validate namespace and path - Self { - namespace, - path, - _phantom: (), - } - } - - 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()) - } - - pub fn from_raw_str(raw: &str) -> Self { - let mut parts = raw.split(":"); - Self::new( - parts.next().expect("Invalid identifier").to_string(), - parts.next().expect("Invalid identifier").to_string(), - ) - } -} - -impl PacketEncodable for Identifier { - fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { - let val: String = self.into(); - val.encode_packet(buf) - } - - fn decode_packet(buf: &mut impl Buf) -> Result { - let str = String::decode_packet(buf)?; - let mut parts = str.split(":"); - - Ok(Self::new( - parts - .next() - .ok_or_else(|| PacketDecodeError::InvalidIdentifier(str.clone()))? - .to_string(), - parts - .next() - .ok_or_else(|| PacketDecodeError::InvalidIdentifier(str.clone()))? - .to_string(), - )) - } -} - -impl From for String { - fn from(value: Identifier) -> Self { - (&value).into() - } -} - -impl From<&Identifier> for String { - fn from(value: &Identifier) -> Self { - format!("{}:{}", value.namespace, value.path) - } -} diff --git a/potato-protocol/src/datatypes/mod.rs b/potato-protocol/src/datatypes/mod.rs index 3ff2ac7..fd16dcb 100644 --- a/potato-protocol/src/datatypes/mod.rs +++ b/potato-protocol/src/datatypes/mod.rs @@ -1,5 +1,4 @@ pub mod byte_array; -pub mod identifier; pub mod pack; pub mod position; pub mod var_int; diff --git a/potato-protocol/src/packet/clientbound/login.rs b/potato-protocol/src/packet/clientbound/login.rs index ab0b14f..7d2f9ad 100644 --- a/potato-protocol/src/packet/clientbound/login.rs +++ b/potato-protocol/src/packet/clientbound/login.rs @@ -1,6 +1,7 @@ +use potato_data::identifier::Identifier; use potato_protocol_derive::{Packet, PacketEncodable}; -use crate::datatypes::{identifier::Identifier, position::Position, var_int::VarInt}; +use crate::datatypes::{position::Position, var_int::VarInt}; #[derive(Debug, Packet)] #[packet(play_id = crate::ids::play::clientbound::LOGIN)] diff --git a/potato-protocol/src/packet/clientbound/registry_data.rs b/potato-protocol/src/packet/clientbound/registry_data.rs index 25b1ae9..a300f92 100644 --- a/potato-protocol/src/packet/clientbound/registry_data.rs +++ b/potato-protocol/src/packet/clientbound/registry_data.rs @@ -1,7 +1,16 @@ +use potato_data::{ + 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, + }, +}; use potato_protocol_derive::{Packet, PacketEncodable}; use serde::{Deserialize, Serialize}; -use crate::{datatypes::identifier::Identifier, packet_encodable::Nbt}; +use crate::packet_encodable::Nbt; #[derive(Debug, Packet)] #[packet(configuration_id = crate::ids::configuration::clientbound::REGISTRY_DATA)] @@ -19,111 +28,37 @@ pub struct RegistryDataEntry { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum RegistryData { - DimensionType(DimensionType), - PaintingVariant(PaintingVariant), - WolfVariant(WolfVariant), - DamegeType(DamageType), - Biome(Biome), + TrimMaterial(Box), + TrimPattern(Box), + BannerPattern(Box), + Biome(Box), + ChatType(Box), + DamageType(Box), + DimensionType(Box), + WolfVariant(Box), + PaintingVariant(Box), } -#[derive(Debug, Serialize, Deserialize)] -pub struct DimensionType { - pub fixed_time: Option, - pub has_skylight: i8, - pub has_ceiling: i8, - pub ultrawarm: i8, - pub natural: i8, - pub coordinate_scale: f64, - pub bed_works: i8, - pub respawn_anchor_works: i8, - pub min_y: i32, - pub height: i32, - pub logical_height: i32, - pub infiniburn: String, // TODO: This is a tag, create a type for these - pub effects: String, // TODO: This is an enum of 3 string variants: - // - mincecraft:overworld - // - minecraft:the_nether - // - minecraft:the_end - pub ambient_light: f32, - pub piglin_safe: i8, - pub has_raids: i8, - pub monster_spawn_light_level: i32, // TODO: This can also be a tag compound of some sort - pub monster_spawn_block_light_limit: i32, +macro_rules! impl_from_registry { + ($($variant:ident),*) => { + $( + impl From<&$variant> for RegistryData { + fn from(value: &$variant) -> Self { + RegistryData::$variant(Box::new(value.clone())) + } + } + )* + }; } -#[derive(Debug, Serialize, Deserialize)] -pub struct PaintingVariant { - pub asset_id: String, - pub height: i32, - pub width: i32, - // TODO: Missing title and author fields -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct WolfVariant { - pub wild_texture: String, - pub tame_texture: String, - pub angry_texture: String, - pub biomes: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct DamageType { - pub message_id: String, - pub scaling: String, - pub exhaustion: f32, - pub effects: Option, - pub death_message_type: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Biome { - pub has_precipitation: i8, - pub temperature: f32, - pub temperature_modifier: Option, - pub downfall: f32, - pub effects: BiomeEffects, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BiomeEffects { - pub fog_color: i32, - pub water_color: i32, - pub water_fog_color: i32, - pub sky_color: i32, - pub foliage_color: Option, - pub grass_color: Option, - pub grass_color_modifier: Option, - pub particle: Option, - pub ambient_sound: Option, // TODO: Can also be a compound tag - pub mood_sound: Option, - pub additions_sound: Option, - pub music: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BiomeParticle { - // TODO: Add fields -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BiomeMoodSound { - pub sound: String, - pub tick_delay: i32, - pub block_search_extent: i32, - pub offset: f64, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BiomeAdditionsSound { - sound: String, - tick_chance: f64, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BiomeMusic { - sound: String, - min_delay: i32, - max_delay: i32, - replace_current_music: i8, -} +impl_from_registry!( + TrimMaterial, + TrimPattern, + BannerPattern, + Biome, + ChatType, + DamageType, + DimensionType, + WolfVariant, + PaintingVariant +); diff --git a/potato-protocol/src/packet_encodable/mod.rs b/potato-protocol/src/packet_encodable/mod.rs index 7d3d981..8d80ada 100644 --- a/potato-protocol/src/packet_encodable/mod.rs +++ b/potato-protocol/src/packet_encodable/mod.rs @@ -1,4 +1,5 @@ use bytes::{Buf, BufMut}; +use potato_data::identifier::Identifier; use serde::{Serialize, de::DeserializeOwned}; use std::fmt; @@ -180,6 +181,29 @@ impl PacketEncodable for bool { } } +impl PacketEncodable for Identifier { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { + let val: String = self.into(); + val.encode_packet(buf) + } + + fn decode_packet(buf: &mut impl Buf) -> Result { + let str = String::decode_packet(buf)?; + let mut parts = str.split(":"); + + Ok(Self::new( + parts + .next() + .ok_or_else(|| PacketDecodeError::InvalidIdentifier(str.clone()))? + .to_string(), + parts + .next() + .ok_or_else(|| PacketDecodeError::InvalidIdentifier(str.clone()))? + .to_string(), + )) + } +} + macro_rules! number_impl { ($($type:ty, $read_fn:tt, $write_fn:tt),* $(,)?) => { $( diff --git a/potato/Cargo.toml b/potato/Cargo.toml index 192f221..cafddb6 100644 --- a/potato/Cargo.toml +++ b/potato/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] potato-protocol = { path = "../potato-protocol" } +potato-data = { path = "../potato-data" } serde.workspace = true serde_json.workspace = true diff --git a/potato/src/connection.rs b/potato/src/connection.rs index de46de0..f248e64 100644 --- a/potato/src/connection.rs +++ b/potato/src/connection.rs @@ -1,16 +1,17 @@ use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; use bytes::{Buf, BytesMut}; +use potato_data::{ + identifier::Identifier, + registry::{Registries, Registry}, +}; use potato_protocol::{ - datatypes::{identifier::Identifier, pack::Pack, var_int::VarInt}, + datatypes::{pack::Pack, var_int::VarInt}, packet::{ Packet, RawPacket, clientbound::{ self, GameEventPacket, SetChunkCacheCenterPacket, - registry_data::{ - Biome, BiomeEffects, DimensionType, PaintingVariant, RegistryData, - RegistryDataEntry, WolfVariant, - }, + registry_data::{RegistryData, RegistryDataEntry}, status_response, }, serverbound, @@ -24,8 +25,6 @@ use tokio::{ sync::{Mutex, RwLock, mpsc::Sender}, }; -use crate::server::Registries; - // Max packet size is 2MB in vanilla const RECV_BUFFER_SIZE: usize = 1024 * 1024 * 2; @@ -405,107 +404,60 @@ impl Client { // } async fn sync_registries(&self, registries: &Arc) -> Result<(), ConnectionError> { - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("dimension_type"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("overworld"), - data: Some(Nbt(RegistryData::DimensionType(DimensionType { - fixed_time: None, - has_skylight: 1, - has_ceiling: 0, - ultrawarm: 0, - natural: 1, - coordinate_scale: 1., - bed_works: 1, - respawn_anchor_works: 0, - min_y: 0, - height: 256, - logical_height: 256, - infiniburn: "#minecraft:infiniburn_overworld".to_owned(), - effects: "minecraft:overworld".to_owned(), - ambient_light: 0., - piglin_safe: 1, - has_raids: 1, - monster_spawn_light_level: 0, - monster_spawn_block_light_limit: 0, - }))), - }], - }) + self.send_registry( + Identifier::minecraft_str("dimension_type"), + ®istries.dimension_types, + ) .await?; - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("painting_variant"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("backyard"), - data: Some(Nbt(RegistryData::PaintingVariant(PaintingVariant { - asset_id: "minecraft:backyard".to_owned(), - height: 2, - width: 2, - }))), - }], - }) + self.send_registry( + Identifier::minecraft_str("painting_variant"), + ®istries.painting_variants, + ) .await?; - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("wolf_variant"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("ashen"), - data: Some(Nbt(RegistryData::WolfVariant(WolfVariant { - wild_texture: "minecraft:entity/wolf/wolf_ashen".to_owned(), - tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_owned(), - angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_owned(), - biomes: vec![], - }))), - }], - }) + self.send_registry( + Identifier::minecraft_str("wolf_variant"), + ®istries.wolf_variants, + ) .await?; - let damage_type_registry_entries = registries - .damage_types - .iter() - .map(|(key, value)| RegistryDataEntry { - id: key.clone(), - data: Some(Nbt(RegistryData::DamegeType(value.clone().into()))), - }) - .collect(); - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("damage_type"), - entries: damage_type_registry_entries, - }) + self.send_registry( + Identifier::minecraft_str("damage_type"), + ®istries.damage_types, + ) .await?; - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("worldgen/biome"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("plains"), - data: Some(Nbt(RegistryData::Biome(Biome { - has_precipitation: 0, - temperature: 0., - temperature_modifier: None, - downfall: 0., - effects: BiomeEffects { - fog_color: 0, - water_color: 0, - water_fog_color: 0, - sky_color: 0, - foliage_color: None, - grass_color: None, - grass_color_modifier: None, - particle: None, - ambient_sound: None, - mood_sound: None, - additions_sound: None, - music: None, - }, - }))), - }], - }) + self.send_registry( + Identifier::minecraft_str("worldgen/biome"), + ®istries.worldgen.biomes, + ) .await?; Ok(()) } + async fn send_registry( + &self, + identifier: Identifier, + registry: &Registry, + ) -> Result<(), ConnectionError> + where + for<'a> &'a T: Into, + { + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: identifier, + entries: registry + .iter() + .map(|(key, value)| RegistryDataEntry { + id: key.clone(), + data: Some(Nbt(value.into())), + }) + .collect(), + }) + .await + } + async fn change_state_num(&self, state: i32) -> Result<(), ConnectionError> { let new_state = match state { 1 => ConnectionState::Status, diff --git a/potato/src/datapack/damage_type.rs b/potato/src/datapack/damage_type.rs deleted file mode 100644 index ce728eb..0000000 --- a/potato/src/datapack/damage_type.rs +++ /dev/null @@ -1,86 +0,0 @@ -use potato_protocol::packet::clientbound::registry_data; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct DamageType { - message_id: String, - scaling: DamageTypeScaling, - exhaustion: f32, - effects: Option, - death_message_type: Option, -} - -// FIXME: It is stupid to have two identical types -impl From for registry_data::DamageType { - fn from(value: DamageType) -> Self { - registry_data::DamageType { - message_id: value.message_id, - scaling: value.scaling.into(), - exhaustion: value.exhaustion, - effects: value.effects.map(Into::::into), - death_message_type: value.death_message_type.map(Into::::into), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "snake_case")] -pub enum DamageTypeScaling { - Never, - WhenCausedByLivingNonPlayer, - Always, -} - -impl From for String { - fn from(value: DamageTypeScaling) -> Self { - match value { - DamageTypeScaling::Never => "never".into(), - DamageTypeScaling::WhenCausedByLivingNonPlayer => { - "when_caused_by_living_non_player".into() - } - DamageTypeScaling::Always => "always".into(), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "snake_case")] -pub enum DamageTypeEffect { - Hurt, - Thorns, - Drowning, - Burning, - Poking, - Freezing, -} - -impl From for String { - fn from(value: DamageTypeEffect) -> Self { - match value { - DamageTypeEffect::Hurt => "hurt".into(), - DamageTypeEffect::Thorns => "thorns".into(), - DamageTypeEffect::Drowning => "drowning".into(), - DamageTypeEffect::Burning => "burning".into(), - DamageTypeEffect::Poking => "poking".into(), - DamageTypeEffect::Freezing => "freezing".into(), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "snake_case")] -pub enum DamageTypeDeathMessageType { - Default, - FallVariants, - IntentionalGameDesign, -} - -impl From for String { - fn from(value: DamageTypeDeathMessageType) -> Self { - match value { - DamageTypeDeathMessageType::Default => "default".into(), - DamageTypeDeathMessageType::FallVariants => "fall_variants".into(), - DamageTypeDeathMessageType::IntentionalGameDesign => "intentional_game_design".into(), - } - } -} diff --git a/potato/src/datapack/mod.rs b/potato/src/datapack/mod.rs deleted file mode 100644 index 14d73d2..0000000 --- a/potato/src/datapack/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -pub mod damage_type; - -use std::{fs::read_to_string, path::PathBuf}; - -use damage_type::DamageType; -use potato_protocol::datatypes::identifier::Identifier; - -use crate::server::{Registries, Registry}; - -pub struct Datapack { - pub name: String, - path: PathBuf, -} - -impl Datapack { - pub fn new(path: PathBuf) -> Self { - // TODO: Load name (and other info) from pack.mcmeta - let name = path.file_name().unwrap().to_str().unwrap().to_string(); - - Self { name, path } - } - - pub fn load(&self, registries: &mut Registries) { - println!("Loading datapack: {}", self.name); - self.load_damage_types(&mut registries.damage_types); - } - - // TODO: Remove the unwraps and deal with errors gracefully - fn load_damage_types(&self, registry: &mut Registry) { - 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().unwrap().to_str().unwrap(); - let path = namespace.join("damage_type"); - if let Ok(files) = std::fs::read_dir(path) { - for file in files { - let file = file.unwrap().path(); - let name = file.file_stem().unwrap().to_str().unwrap(); - let identfiier = Identifier::new_str(namespace_str, name); - - let data: DamageType = serde_json::from_str(&read_to_string(file).unwrap()) - .expect("Failed to parse damage type"); - - registry.insert(identfiier, data); - } - } - } - } -} diff --git a/potato/src/main.rs b/potato/src/main.rs index d2f4635..6a82744 100644 --- a/potato/src/main.rs +++ b/potato/src/main.rs @@ -1,5 +1,4 @@ pub mod connection; -pub mod datapack; pub mod player; pub mod server; diff --git a/potato/src/server.rs b/potato/src/server.rs index 94ef26f..a104740 100644 --- a/potato/src/server.rs +++ b/potato/src/server.rs @@ -1,8 +1,6 @@ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; -use potato_protocol::datatypes::identifier::Identifier; - -use crate::datapack::{Datapack, damage_type::DamageType}; +use potato_data::{datapack::Datapack, registry::Registries}; pub struct Server { pub registries: Arc, @@ -21,27 +19,28 @@ impl Server { println!("Found {} datapacks", datapacks.len()); println!("Loading datapacks..."); let mut registries = Registries::empty(); - datapacks.iter().for_each(|pack| pack.load(&mut registries)); + datapacks + .iter() + .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()); Server { registries: Arc::new(registries), } } } - -pub type Registry = HashMap; - -pub struct Registries { - pub damage_types: Registry, -} - -impl Registries { - fn empty() -> Registries { - Registries { - damage_types: HashMap::new(), - } - } -}