From 7e46ec7f5954a5a16f9426efdc1ee16c8cd2de2e Mon Sep 17 00:00:00 2001 From: koskja <87826472+koskja@users.noreply.github.com> Date: Sat, 11 Sep 2021 16:38:37 +0200 Subject: [PATCH] Datapack parsing (#455) --- .gitignore | 2 + Cargo.lock | 48 ++- feather/blocks/src/lib.rs | 2 - feather/common/Cargo.toml | 1 + feather/common/src/game.rs | 7 + feather/datapacks/Cargo.toml | 13 +- feather/datapacks/build.rs | 8 + feather/datapacks/src/id.rs | 14 + feather/datapacks/src/lib.rs | 62 ++- feather/datapacks/src/recipe.rs | 364 +++++++++++++++++ feather/datapacks/src/serde_helpers.rs | 71 ++++ feather/datapacks/src/tag.rs | 376 ++++++++++++++++++ feather/datapacks/vanilla_assets/Cargo.toml | 13 + .../vanilla.rs => vanilla_assets/src/lib.rs} | 20 +- feather/generated/generators/tags.py | 33 ++ feather/generated/src/lib.rs | 2 + feather/generated/src/vanilla_tags.rs | 355 +++++++++++++++++ feather/server/Cargo.toml | 1 + feather/server/src/client.rs | 4 +- feather/server/src/main.rs | 17 +- feather/server/src/systems/player_join.rs | 2 +- 21 files changed, 1393 insertions(+), 22 deletions(-) create mode 100644 feather/datapacks/build.rs create mode 100644 feather/datapacks/src/recipe.rs create mode 100644 feather/datapacks/src/serde_helpers.rs create mode 100644 feather/datapacks/src/tag.rs create mode 100644 feather/datapacks/vanilla_assets/Cargo.toml rename feather/datapacks/{src/vanilla.rs => vanilla_assets/src/lib.rs} (84%) create mode 100644 feather/generated/generators/tags.py create mode 100644 feather/generated/src/vanilla_tags.rs diff --git a/.gitignore b/.gitignore index 79c1a3956..b666bdd52 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ .cargo world/ +feather/downloaded +feather/datapacks/minecraft/ /config.toml # Python cache files (libcraft) diff --git a/Cargo.lock b/Cargo.lock index 16f9b1c87..0b0cadc7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ dependencies = [ "anyhow", "feather-base", "feather-blocks", + "feather-datapacks", "feather-ecs", "feather-generated", "feather-utils", @@ -834,13 +835,16 @@ version = "0.1.0" dependencies = [ "ahash 0.4.7", "anyhow", + "feather-blocks", + "feather-generated", + "feather-protocol", + "feather-vanilla-assets", "log", "serde", "serde_json", "smartstring", "thiserror", - "ureq", - "zip", + "walkdir", ] [[package]] @@ -935,6 +939,7 @@ dependencies = [ "crossbeam-utils", "feather-base", "feather-common", + "feather-datapacks", "feather-ecs", "feather-plugin-host", "feather-protocol", @@ -973,6 +978,16 @@ dependencies = [ "simple_logger", ] +[[package]] +name = "feather-vanilla-assets" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "ureq", + "zip", +] + [[package]] name = "feather-worldgen" version = "0.6.0" @@ -2379,6 +2394,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -3041,6 +3065,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3395,6 +3430,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/feather/blocks/src/lib.rs b/feather/blocks/src/lib.rs index a3ee8b10c..43311335f 100644 --- a/feather/blocks/src/lib.rs +++ b/feather/blocks/src/lib.rs @@ -94,7 +94,6 @@ impl BlockId { VANILLA_ID_TABLE[self.kind as u16 as usize][self.state as usize] } - /* /// Returns the vanilla fluid ID for this block in case it is a fluid. /// The fluid ID is used in the Tags packet. pub fn vanilla_fluid_id(self) -> Option { @@ -112,7 +111,6 @@ impl BlockId { None } } - */ /// Returns the block corresponding to the given vanilla ID. /// diff --git a/feather/common/Cargo.toml b/feather/common/Cargo.toml index 4560fb162..beb409ec3 100644 --- a/feather/common/Cargo.toml +++ b/feather/common/Cargo.toml @@ -22,4 +22,5 @@ uuid = { version = "0.8", features = [ "v4" ] } libcraft-core = { path = "../../libcraft/core" } rayon = "1.5" worldgen = { path = "../worldgen", package = "feather-worldgen" } +datapacks = { path = "../datapacks", package = "feather-datapacks" } rand = "0.8" \ No newline at end of file diff --git a/feather/common/src/game.rs b/feather/common/src/game.rs index 3d23442dc..3ab8f0b47 100644 --- a/feather/common/src/game.rs +++ b/feather/common/src/game.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, mem, rc::Rc, sync::Arc}; use base::{BlockId, BlockPosition, ChunkPosition, Position, Text, Title}; +use datapacks::{RecipeRegistry, TagRegistry}; use ecs::{ Ecs, Entity, EntityBuilder, HasEcs, HasResources, NoSuchEntity, Resources, SysResult, SystemExecutor, @@ -54,6 +55,10 @@ pub struct Game { entity_spawn_callbacks: Vec, entity_builder: EntityBuilder, + + pub tag_registry: TagRegistry, + + pub recipe_registry: RecipeRegistry, } impl Default for Game { @@ -74,6 +79,8 @@ impl Game { tick_count: 0, entity_spawn_callbacks: Vec::new(), entity_builder: EntityBuilder::new(), + tag_registry: TagRegistry::new(), + recipe_registry: RecipeRegistry::new(), } } diff --git a/feather/datapacks/Cargo.toml b/feather/datapacks/Cargo.toml index a31e24751..363d30e32 100644 --- a/feather/datapacks/Cargo.toml +++ b/feather/datapacks/Cargo.toml @@ -1,16 +1,21 @@ [package] name = "feather-datapacks" version = "0.1.0" -authors = [ "caelunshun " ] +authors = [ "koskja ", "caelunshun " ] edition = "2018" [dependencies] ahash = "0.4" -anyhow = "1" log = "0.4" serde = { version = "1", features = [ "derive" ] } serde_json = "1" smartstring = { version = "0.2", features = [ "serde" ] } thiserror = "1" -ureq = { version = "2", default-features = false, features = [ "tls" ] } -zip = "0.5" +generated = { path = "../generated", package = "feather-generated" } +blocks = { path = "../blocks", package = "feather-blocks"} +protocol = { path = "../protocol", package = "feather-protocol" } +walkdir = "2.3.2" + +[build-dependencies] +vanilla-assets = { path = "./vanilla_assets", package = "feather-vanilla-assets" } +anyhow = "1" \ No newline at end of file diff --git a/feather/datapacks/build.rs b/feather/datapacks/build.rs new file mode 100644 index 000000000..104f12001 --- /dev/null +++ b/feather/datapacks/build.rs @@ -0,0 +1,8 @@ +use std::path::PathBuf; + +fn main() -> anyhow::Result<()> { + if !PathBuf::from("./minecraft").exists() { + vanilla_assets::download_vanilla_assets(&PathBuf::from("../"))?; + } + Ok(()) +} diff --git a/feather/datapacks/src/id.rs b/feather/datapacks/src/id.rs index e0e9beebb..9027a7b5d 100644 --- a/feather/datapacks/src/id.rs +++ b/feather/datapacks/src/id.rs @@ -29,6 +29,20 @@ impl NamespacedId { pub fn name(&self) -> &str { &self.name } + + pub fn from_parts(namespace: &str, name: &str) -> Result { + let namespace = if namespace.is_empty() { + DEFAULT_NAMESPACE + } else { + namespace + }; + validate_namespace(namespace)?; + validate_name(name)?; + Ok(Self { + namespace: SmartString::from(namespace), + name: SmartString::from(name), + }) + } } /// Error returned when a namespaced ID was formatted incorrectly. diff --git a/feather/datapacks/src/lib.rs b/feather/datapacks/src/lib.rs index a0c705819..915df7eb0 100644 --- a/feather/datapacks/src/lib.rs +++ b/feather/datapacks/src/lib.rs @@ -6,19 +6,55 @@ //! This crate also downloads vanilla JARs and assets //! at startup; see `download_vanilla_assets`. +use std::path::Path; + use ahash::AHashMap; +use id::ParseError; use serde::Deserialize; use smartstring::{LazyCompact, SmartString}; -mod vanilla; -pub use vanilla::download_vanilla_assets; - mod id; pub use id::NamespacedId; +mod serde_helpers; +pub(crate) use serde_helpers::*; + +pub mod tag; +use tag::LoopError; +pub use tag::{TagRegistry, TagRegistryBuilder}; + +pub mod recipe; +pub use recipe::RecipeRegistry; + /// The default namespace for resource locations (NamespacedIds). pub const DEFAULT_NAMESPACE: &str = "minecraft"; +use thiserror::Error; +#[derive(Error, Debug)] +pub enum TagLoadError { + #[error("invalid namespaced id: {0}")] + Parse(#[from] ParseError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("io error: {0}")] + WalkDir(#[from] walkdir::Error), + #[error("loop detected when parsing tags: {0}")] + FoundLoop(#[from] LoopError), + #[error("invalid tag link: {0} references {1}")] + InvalidLink(NamespacedId, NamespacedId), + #[error("json parsing error: {0}")] + Json(#[from] serde_json::Error), +} +#[derive(Error, Debug)] +pub enum RecipeLoadError { + #[error("invalid namespaced id: {0}")] + Parse(#[from] ParseError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("json parsing error: {0}")] + Json(#[from] serde_json::Error), +} + /// The pack.mcmeta file at the root of a datapack. /// /// Formatted with JSON. @@ -33,3 +69,23 @@ pub struct Datapacks { /// The metadata of loaded packs. Keyed by the datapack name. _meta: AHashMap, PackMeta>, } +#[derive(Default)] +pub struct Datapack { + pub advancements: (), + pub loot_tables: (), + pub recipes: (), + pub structures: (), + pub tags: TagRegistry, +} + +impl Datapack { + pub fn new() -> Self { + Self { + ..Default::default() + } + } + pub fn from_folder(dir: &Path) -> Self { + assert!(dir.is_dir(), "not a directory"); + todo!() + } +} diff --git a/feather/datapacks/src/recipe.rs b/feather/datapacks/src/recipe.rs new file mode 100644 index 000000000..296295367 --- /dev/null +++ b/feather/datapacks/src/recipe.rs @@ -0,0 +1,364 @@ +use std::{collections::HashMap, fs::File, io::Read, path::Path}; + +use crate::{NamespacedId, SerdeItem, SerdeItemStack, TagRegistry}; +use generated::{Item, ItemStack}; +use serde::{Deserialize, Serialize}; +use smartstring::{Compact, SmartString}; +/// A registry for keeping track of recipes. +#[derive(Clone, Debug, Default)] +pub struct RecipeRegistry { + pub blast: Vec, + pub camp: Vec, + pub shaped: Vec, + pub shapeless: Vec, + pub smelt: Vec, + pub smith: Vec, + pub smoke: Vec, + pub stone: Vec, +} +impl RecipeRegistry { + pub fn new() -> Self { + Self { + ..Default::default() + } + } + pub fn from_dir(path: &Path) -> Result { + let mut this = Self::new(); + this.add_from_dir(path)?; + Ok(this) + } + pub fn add_from_dir(&mut self, path: &Path) -> Result<(), crate::RecipeLoadError> { + for file in std::fs::read_dir(path)? { + let path = file?.path(); + log::trace!("{}", path.to_string_lossy()); + match Recipe::from_file(&path)? { + Recipe::Blasting(recipe) => self.blast.push(recipe), + Recipe::Campfire(recipe) => self.camp.push(recipe), + Recipe::Shaped(recipe) => self.shaped.push(recipe), + Recipe::Shapeless(recipe) => self.shapeless.push(recipe), + Recipe::Smelting(recipe) => self.smelt.push(recipe), + Recipe::Smithing(recipe) => self.smith.push(recipe), + Recipe::Smoking(recipe) => self.smoke.push(recipe), + Recipe::Stonecutting(recipe) => self.stone.push(recipe), + Recipe::Special => {} + } + } + Ok(()) + } + pub fn match_blasting(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + self.blast + .iter() + .find_map(|r| r.match_self(item, tag_registry)) + } + pub fn match_campfire_cooking( + &self, + item: Item, + tag_registry: &TagRegistry, + ) -> Option<(Item, f32)> { + self.camp + .iter() + .find_map(|r| r.match_self(item, tag_registry)) + } + pub fn match_shapeless<'a>( + &self, + items: impl Iterator, + tag_registry: &TagRegistry, + ) -> Option { + let items: Vec = items.copied().collect(); + self.shapeless + .iter() + .find_map(|r| r.match_self(items.iter(), tag_registry)) + } + pub fn match_smelting(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + self.smelt + .iter() + .find_map(|r| r.match_self(item, tag_registry)) + } + pub fn match_smithing( + &self, + base: Item, + addition: Item, + tag_registry: &TagRegistry, + ) -> Option { + self.smith + .iter() + .find_map(|r| r.match_self(base, addition, tag_registry)) + } + pub fn match_smoking(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + self.smoke + .iter() + .find_map(|r| r.match_self(item, tag_registry)) + } + pub fn match_stonecutting(&self, item: Item, tag_registry: &TagRegistry) -> Option { + self.stone + .iter() + .find_map(|r| r.match_self(item, tag_registry)) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum Recipe { + #[serde(rename = "minecraft:blasting")] + Blasting(BlastingRecipe), + #[serde(rename = "minecraft:campfire_cooking")] + Campfire(CampfireRecipe), + #[serde(rename = "minecraft:crafting_shaped")] + Shaped(ShapedRecipe), + #[serde(rename = "minecraft:crafting_shapeless")] + Shapeless(ShapelessRecipe), + #[serde(rename = "minecraft:smelting")] + Smelting(SmeltingRecipe), + #[serde(rename = "minecraft:smithing")] + Smithing(SmithingRecipe), + #[serde(rename = "minecraft:smoking")] + Smoking(SmokingRecipe), + #[serde(rename = "minecraft:stonecutting")] + Stonecutting(StonecuttingRecipe), + #[serde(alias = "minecraft:crafting_special_armordye")] + #[serde(alias = "minecraft:crafting_special_bannerduplicate")] + #[serde(alias = "minecraft:crafting_special_bookcloning")] + #[serde(alias = "minecraft:crafting_special_firework_rocket")] + #[serde(alias = "minecraft:crafting_special_firework_star")] + #[serde(alias = "minecraft:crafting_special_firework_star_fade")] + #[serde(alias = "minecraft:crafting_special_mapcloning")] + #[serde(alias = "minecraft:crafting_special_mapextending")] + #[serde(alias = "minecraft:crafting_special_repairitem")] + #[serde(alias = "minecraft:crafting_special_shielddecoration")] + #[serde(alias = "minecraft:crafting_special_shulkerboxcoloring")] + #[serde(alias = "minecraft:crafting_special_tippedarrow")] + #[serde(alias = "minecraft:crafting_special_suspiciousstew")] + Special, +} +impl Recipe { + pub fn from_file(path: &Path) -> Result { + let mut s = String::new(); + File::open(path)?.read_to_string(&mut s)?; + let k = serde_json::from_str(&s)?; + Ok(k) + } +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct Single { + item: Option, + tag: Option, +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +enum Ingredient { + One(Single), + Array(Vec), +} +impl Single { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.item + .as_ref() + .map(|s| item.name() == s.name()) + .unwrap_or(false) + | self + .tag + .as_ref() + .map(|s| tag_registry.check_item_tag(item, s)) + .unwrap_or(false) + } +} +impl Ingredient { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + match self { + Ingredient::One(o) => o.matches(item, tag_registry), + Ingredient::Array(vec) => vec.iter().any(|o| o.matches(item, tag_registry)), + } + } +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SmeltingRecipe { + group: Option>, + ingredient: Ingredient, + result: SerdeItem, + experience: f32, + #[serde(default = "default_smelting_time")] + cookingtime: u32, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SmokingRecipe { + group: Option>, + ingredient: Ingredient, + result: SerdeItem, + experience: f32, + #[serde(default = "default_smoking_time")] + cookingtime: u32, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlastingRecipe { + group: Option>, + ingredient: Ingredient, + result: SerdeItem, + experience: f32, + #[serde(default = "default_blasting_time")] + cookingtime: u32, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CampfireRecipe { + group: Option>, + ingredient: Ingredient, + result: SerdeItem, + experience: f32, + #[serde(default = "default_campfire_time")] + cookingtime: u32, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ShapelessRecipe { + group: Option>, + ingredients: Vec, + result: SerdeItemStack, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ShapedRecipe { + group: Option>, + pattern: Vec>, + key: HashMap, Ingredient>, + result: SerdeItemStack, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SmithingRecipe { + group: Option>, + base: Ingredient, + addition: Ingredient, + result: SerdeItemStack, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StonecuttingRecipe { + group: Option>, + ingredient: Ingredient, + result: SerdeItem, + count: u32, +} +impl SmeltingRecipe { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.ingredient.matches(item, tag_registry) + } + pub fn match_self(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + if self.matches(item, tag_registry) { + Some((self.result.into(), self.experience)) + } else { + None + } + } +} +impl SmokingRecipe { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.ingredient.matches(item, tag_registry) + } + pub fn match_self(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + if self.matches(item, tag_registry) { + Some((self.result.into(), self.experience)) + } else { + None + } + } +} +impl BlastingRecipe { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.ingredient.matches(item, tag_registry) + } + pub fn match_self(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + if self.matches(item, tag_registry) { + Some((self.result.into(), self.experience)) + } else { + None + } + } +} +impl CampfireRecipe { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.ingredient.matches(item, tag_registry) + } + pub fn match_self(&self, item: Item, tag_registry: &TagRegistry) -> Option<(Item, f32)> { + if self.matches(item, tag_registry) { + Some((self.result.into(), self.experience)) + } else { + None + } + } +} +impl ShapelessRecipe { + pub fn matches<'a>( + &self, + items: impl Iterator, + tag_registry: &TagRegistry, + ) -> bool { + let mut counter = self.ingredients.clone(); + for i in items { + match counter + .iter() + .enumerate() + .find(|(_, ing)| ing.matches(*i, tag_registry)) + { + Some((index, _)) => { + counter.remove(index); + } + None => return false, + }; + } + true + } + pub fn match_self<'a>( + &self, + items: impl Iterator, + tag_registry: &TagRegistry, + ) -> Option { + if self.matches(items, tag_registry) { + Some(self.result.into()) + } else { + None + } + } +} +impl ShapedRecipe { + // TODO: Decide how to pass the crafting grid +} +impl SmithingRecipe { + pub fn matches(&self, base: Item, addition: Item, tag_registry: &TagRegistry) -> bool { + self.base.matches(base, tag_registry) && self.addition.matches(addition, tag_registry) + } + pub fn match_self( + &self, + base: Item, + addition: Item, + tag_registry: &TagRegistry, + ) -> Option { + if self.matches(base, addition, tag_registry) { + Some(self.result.item.into()) + } else { + None + } + } +} +impl StonecuttingRecipe { + pub fn matches(&self, item: Item, tag_registry: &TagRegistry) -> bool { + self.ingredient.matches(item, tag_registry) + } + pub fn match_self(&self, item: Item, tag_registry: &TagRegistry) -> Option { + if self.matches(item, tag_registry) { + Some(ItemStack { + item: self.result.into(), + count: self.count, + damage: None, + }) + } else { + None + } + } +} +pub fn default_smelting_time() -> u32 { + 200 +} +pub fn default_smoking_time() -> u32 { + 100 +} +pub fn default_blasting_time() -> u32 { + 100 +} +pub fn default_campfire_time() -> u32 { + 100 +} diff --git a/feather/datapacks/src/serde_helpers.rs b/feather/datapacks/src/serde_helpers.rs new file mode 100644 index 000000000..a300396a6 --- /dev/null +++ b/feather/datapacks/src/serde_helpers.rs @@ -0,0 +1,71 @@ +use generated::{Item, ItemStack}; +use serde::{Deserialize, Serialize}; + +pub fn default_count() -> u32 { + 1 +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SerdeItem(Item); + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] +pub struct SerdeItemStack { + pub item: SerdeItem, + #[serde(default = "default_count")] + pub count: u32, + pub damage: Option, +} +impl From for ItemStack { + fn from(s: SerdeItemStack) -> Self { + Self { + item: s.item.into(), + count: s.count, + damage: s.damage, + } + } +} +impl From for SerdeItemStack { + fn from(s: ItemStack) -> Self { + Self { + item: s.item.into(), + count: s.count, + damage: s.damage, + } + } +} +impl From for Item { + fn from(s: SerdeItem) -> Self { + s.0 + } +} +impl From for SerdeItem { + fn from(s: Item) -> Self { + Self(s) + } +} + +impl Serialize for SerdeItem { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut s = self.0.name().to_owned(); + s.insert_str(0, "minecraft:"); + s.serialize(serializer) + } +} +impl<'de> Deserialize<'de> for SerdeItem { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + Ok(Self( + Item::from_name(match string.strip_prefix("minecraft:") { + Some(s) => s, + None => &string, + }) + .unwrap(), + )) + } +} diff --git a/feather/datapacks/src/tag.rs b/feather/datapacks/src/tag.rs new file mode 100644 index 000000000..6f4800208 --- /dev/null +++ b/feather/datapacks/src/tag.rs @@ -0,0 +1,376 @@ +use std::{ + borrow::Borrow, cell::RefCell, collections::VecDeque, fmt::Display, fs::File, io::Read, + path::Path, str::FromStr, +}; + +use crate::NamespacedId; +use ahash::{AHashMap, AHashSet}; +use blocks::BlockId; +use generated::{BlockKind, EntityKind, Item}; +use protocol::{ + packets::server::{AllTags, Tag}, + VarInt, +}; +use serde::Deserialize; +use smartstring::{Compact, SmartString}; +use thiserror::Error; +use walkdir::WalkDir; + +pub use generated::vanilla_tags::*; +/// The tag registry builder's purpose is to serve as a stepping stone to construct the full tag registry. +/// Once all datapacks are loaded, the builder resolves all tag "symlinks". +/// An example of this behaviour is the tag `#minecraft:fences`, which includes `minecraft:nether_brick_fence` and `#minecraft:wooden_fences`. +#[derive(Debug, Default)] +pub struct TagRegistryBuilder { + block_map: AHashMap>>, + entity_map: AHashMap>>, + fluid_map: AHashMap>>, + item_map: AHashMap>>, +} + +impl TagRegistryBuilder { + pub fn new() -> Self { + Self { + ..Default::default() + } + } + pub fn add_tags_from_dir( + &mut self, + dir: &Path, + namespace: &str, + ) -> Result<(), crate::TagLoadError> { + assert!(dir.is_dir()); + let blocks = dir.join("blocks"); + let entity_types = dir.join("entity_types"); + let fluids = dir.join("fluids"); + let items = dir.join("items"); + if blocks.exists() { + Self::fill_map(&blocks, &mut self.block_map, namespace)?; + } + if entity_types.exists() { + Self::fill_map(&entity_types, &mut self.entity_map, namespace)?; + } + if fluids.exists() { + Self::fill_map(&fluids, &mut self.fluid_map, namespace)?; + } + if items.exists() { + Self::fill_map(&items, &mut self.item_map, namespace)?; + } + Ok(()) + } + pub fn from_dir(dir: &Path, namespace: &str) -> Result { + let mut this = Self::new(); + this.add_tags_from_dir(dir, namespace)?; + Ok(this) + } + fn fill_map( + dir: &Path, + map: &mut AHashMap>>, + namespace: &str, + ) -> Result<(), crate::TagLoadError> { + for entry in WalkDir::new(dir).into_iter() { + let entry = entry?; + let entry = entry.path(); + if !entry.is_file() { + continue; + } + log::trace!("{}", entry.to_string_lossy()); + let path_to_file = entry.parent().unwrap(); + let file_name = entry.file_stem().unwrap(); + let tag_name = std::borrow::Cow::Owned( + path_to_file + .strip_prefix(dir) + .unwrap() + .to_string_lossy() + .replace("\\", "/"), + ) + file_name.to_string_lossy(); + let namespaced = NamespacedId::from_parts(namespace, &tag_name[..])?; + if !map.contains_key(&namespaced) { + map.insert(namespaced.clone(), Default::default()); + } + Self::fill_set(entry, map.get_mut(&namespaced).unwrap())?; + } + Ok(()) + } + fn fill_set( + file: &Path, + set: &mut AHashSet>, + ) -> Result<(), crate::TagLoadError> { + assert!(file.is_file()); + let mut s = String::new(); + File::open(file).unwrap().read_to_string(&mut s).unwrap(); + let file: TagFile = serde_json::from_str(&s[..])?; + if file.replace { + set.clear(); + } + for entry in file.values { + set.insert(SmartString::from(entry)); + } + Ok(()) + } + fn parse( + source: &AHashMap>>, + target: &mut AHashMap>, + ) -> Result<(), crate::TagLoadError> { + let mut stack = VecDeque::new(); + for tag in source.keys().cloned() { + if target.contains_key(&tag) { + continue; + } + Self::parse_rec(tag, &mut stack, source, target)?; + } + Ok(()) + } + fn parse_rec( + tag: NamespacedId, + stack: &mut VecDeque, + source: &AHashMap>>, + target: &mut AHashMap>, + ) -> Result<(), crate::TagLoadError> { + if stack.contains(&tag) { + return Err(LoopError(stack.iter().cloned().collect()).into()); + } + let set = match source.get(&tag) { + Some(s) => s, + None => { + return Err(crate::TagLoadError::InvalidLink( + stack.pop_back().unwrap(), + tag, + )) + } + }; + assert!(target.insert(tag.clone(), Default::default()).is_none()); + // Parse all child tags + for child in set.iter().filter_map(|s| s.strip_prefix('#')) { + let child = NamespacedId::from_str(child)?; + if !target.contains_key(&child) { + // Skip already parsed tags + stack.push_back(tag.clone()); + Self::parse_rec(child.clone(), stack, source, target)?; + } + for element in target.get(&child).unwrap().clone() { + // Insert child entry + target.get_mut(&tag).unwrap().insert(element); + } + } + let target_entry = target.get_mut(&tag).unwrap(); + + for i in source + .get(&tag) + .unwrap() + .iter() + .filter(|e| !e.starts_with('#')) + { + // Insert all non-tag entries + target_entry.insert(NamespacedId::from_str(i)?); + } + // This tag is now parsed. + stack.pop_back(); + + Ok(()) + } + pub fn build(self) -> Result { + let mut res = TagRegistry::new(); + Self::parse(&self.block_map, &mut res.block_map)?; + Self::parse(&self.entity_map, &mut res.entity_map)?; + Self::parse(&self.fluid_map, &mut res.fluid_map)?; + Self::parse(&self.item_map, &mut res.item_map)?; + Ok(res) + } +} +/// A registry for keeping track of tags. +#[derive(Debug, Default)] +pub struct TagRegistry { + block_map: AHashMap>, + entity_map: AHashMap>, + fluid_map: AHashMap>, + item_map: AHashMap>, + cached_packet: RefCell>>, +} +impl TagRegistry { + pub fn new() -> Self { + Self { + ..Default::default() + } + } + pub fn check_block_tag(&self, block: BlockKind, tag: &T) -> bool + where + T: Into + Clone, + { + self.block_map + .get(&tag.clone().into()) + .map(|set| set.get(&NamespacedId::from_str(block.name()).unwrap())) + .flatten() + .is_some() + } + pub fn check_entity_tag(&self, entity: EntityKind, tag: &T) -> bool + where + T: Into + Clone, + { + self.entity_map + .get(&tag.clone().into()) + .map(|set| set.get(&NamespacedId::from_str(entity.name()).unwrap())) + .flatten() + .is_some() + } + pub fn check_fluid_tag(&self, fluid: BlockKind, tag: &T) -> bool + where + T: Into + Clone, + { + self.fluid_map + .get(&tag.clone().into()) + .map(|set| set.get(&NamespacedId::from_str(fluid.name()).unwrap())) + .flatten() + .is_some() + } + pub fn check_item_tag(&self, item: Item, tag: &T) -> bool + where + T: Into + Clone, + { + self.item_map + .get(&tag.clone().into()) + .map(|set| set.get(&NamespacedId::from_str(item.name()).unwrap())) + .flatten() + .is_some() + } + pub fn check_for_any_tag(&self, thing: impl Borrow, tag: &T) -> bool + where + T: Into + Clone, + { + let thing = NamespacedId::from_str(thing.borrow()).unwrap(); + let tag = tag.clone().into(); + self.block_map.get(&tag).map(|s| s.get(&thing)).is_some() + | self.entity_map.get(&tag).map(|s| s.get(&thing)).is_some() + | self.fluid_map.get(&tag).map(|s| s.get(&thing)).is_some() + | self.item_map.get(&tag).map(|s| s.get(&thing)).is_some() + } + /// Provides an `AllTags` packet for sending to the client. This tag is cached to save some performance. + pub fn all_tags(&self) -> AllTags { + let mut inner = self.cached_packet.borrow_mut(); + if inner.is_some() { + inner.as_ref().unwrap().as_ref().to_owned() + } else { + let tags = self.build_tags_packet(); + *inner = Some(Box::new(tags.clone())); + tags + } + } + fn build_tags_packet(&self) -> AllTags { + let mut block_tags = vec![]; + let mut entity_tags = vec![]; + let mut fluid_tags = vec![]; + let mut item_tags = vec![]; + for (tag_name, block_names) in &self.block_map { + block_tags.push(Tag { + name: tag_name.to_string(), + entries: block_names + .iter() + .map(|e| VarInt(generated::BlockKind::from_name(e.name()).unwrap().id() as i32)) + .collect(), + }); + } + for (tag_name, entity_names) in &self.entity_map { + entity_tags.push(Tag { + name: tag_name.to_string(), + entries: entity_names + .iter() + .map( + |e| VarInt(generated::EntityKind::from_name(e.name()).unwrap().id() as i32), + ) + .collect(), + }); + } + for (tag_name, fluid_names) in &self.fluid_map { + let mut entries = vec![]; + for entry in fluid_names { + let block = match BlockId::from_identifier(&entry.to_string()) { + Some(s) => s, + None => BlockId::from_identifier(&entry.to_string().replace("flowing_", "")) + .unwrap() + .with_water_level(1), + }; + entries.push(VarInt(block.vanilla_fluid_id().unwrap() as i32)); + } + fluid_tags.push(Tag { + name: tag_name.to_string(), + entries, + }); + } + for (tag_name, item_names) in &self.item_map { + item_tags.push(Tag { + name: tag_name.to_string(), + entries: item_names + .iter() + .map(|e| VarInt(generated::Item::from_name(e.name()).unwrap().id() as i32)) + .collect(), + }); + } + AllTags { + block_tags, + item_tags, + fluid_tags, + entity_tags, + } + } + fn display_helper( + map: &AHashMap>, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let mut m = map.iter().collect::>(); + m.sort_by(|a, b| a.0.cmp(b.0)); + for (a, b) in m { + writeln!(f, "{}: ", a)?; + let mut n = b.iter().collect::>(); + n.sort(); + for c in n { + writeln!(f, " {}", c)?; + } + } + Ok(()) + } +} +impl Display for TagRegistry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Self::display_helper(&self.block_map, f)?; + Self::display_helper(&self.entity_map, f)?; + Self::display_helper(&self.fluid_map, f)?; + Self::display_helper(&self.item_map, f)?; + + Ok(()) + } +} +#[derive(Deserialize)] +struct TagFile { + pub replace: bool, + pub values: Vec, +} +#[derive(Debug, Error)] +pub struct LoopError(Vec); +impl Display for LoopError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for entry in &self.0 { + writeln!(f, "{}", entry)?; + } + Ok(()) + } +} +impl From for crate::NamespacedId { + fn from(tag: VanillaBlockTags) -> Self { + NamespacedId::from_str(tag.name()).unwrap() + } +} +impl From for crate::NamespacedId { + fn from(tag: VanillaEntityTypes) -> Self { + NamespacedId::from_str(tag.name()).unwrap() + } +} +impl From for crate::NamespacedId { + fn from(tag: VanillaFluidTags) -> Self { + NamespacedId::from_str(tag.name()).unwrap() + } +} +impl From for crate::NamespacedId { + fn from(tag: VanillaItemTags) -> Self { + NamespacedId::from_str(tag.name()).unwrap() + } +} diff --git a/feather/datapacks/vanilla_assets/Cargo.toml b/feather/datapacks/vanilla_assets/Cargo.toml new file mode 100644 index 000000000..d64698656 --- /dev/null +++ b/feather/datapacks/vanilla_assets/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "feather-vanilla-assets" +version = "0.1.0" +authors = [ "caelunshun " ] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +ureq = { version = "2", default-features = false, features = [ "tls" ] } +zip = "0.5" +log = "0.4" diff --git a/feather/datapacks/src/vanilla.rs b/feather/datapacks/vanilla_assets/src/lib.rs similarity index 84% rename from feather/datapacks/src/vanilla.rs rename to feather/datapacks/vanilla_assets/src/lib.rs index ec051ce93..a26a2dc6f 100644 --- a/feather/datapacks/src/vanilla.rs +++ b/feather/datapacks/vanilla_assets/src/lib.rs @@ -21,7 +21,6 @@ pub fn download_vanilla_assets(base: &Path) -> anyhow::Result<()> { let jar = download_jar(base) .context("failed to download vanilla server JAR") .context("please make sure you have an Internet connection.")?; - // NB: JAR files are just glorified ZIP files, so we can use the zip crate // to process the data. let mut zip = ZipArchive::new(jar)?; @@ -40,7 +39,12 @@ fn download_jar(base: &Path) -> anyhow::Result { let downloaded_dir = base.join("downloaded"); fs::create_dir_all(&downloaded_dir)?; - let mut file = File::create(downloaded_dir.join(JAR_NAME))?; + let mut file = fs::OpenOptions::new() + .write(true) + .read(true) + .truncate(true) + .create(true) + .open(downloaded_dir.join(JAR_NAME))?; io::copy(&mut data, &mut file)?; Ok(file) @@ -62,10 +66,14 @@ fn create_minecraft_datapack(base: &Path, zip: &mut ZipArchive) -> anyhow: // copy data directory let mut files = Vec::new(); - for file_name in zip.file_names() { - let path = Path::new(file_name); - let path_in_target = fs::canonicalize(target.join(path))?; - if path.starts_with("data") && path_in_target.starts_with(&target) { + for file_name in zip + .file_names() + .map(|s| s.to_owned()) + .collect::>() + { + let path = Path::new(&file_name); + if path.starts_with("data") && zip.by_name(&file_name)?.is_file() { + let path_in_target = target.join(path); files.push((file_name.to_owned(), path_in_target)); } } diff --git a/feather/generated/generators/tags.py b/feather/generated/generators/tags.py new file mode 100644 index 000000000..8ebc8e2ce --- /dev/null +++ b/feather/generated/generators/tags.py @@ -0,0 +1,33 @@ +from os import listdir +import common +prefix = "../datapacks/minecraft/data/minecraft/tags/" +block_tags = listdir(prefix + "blocks") +entity_types = listdir(prefix + "entity_types") +fluid_tags = listdir(prefix + "fluids") +item_tags = listdir(prefix + "items") +tag_list_list = (block_tags, entity_types, fluid_tags, item_tags) +enum_names = ("VanillaBlockTags", "VanillaEntityTypes", "VanillaFluidTags", "VanillaItemTags") +new_line = "\n" +quotes = "\"" +output = "" +for (tags, enum_name) in zip(tag_list_list, enum_names): + output += "#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n" + output += f"pub enum {enum_name} {{{new_line}" + for s in tags: + camelcase = ''.join(map(str.title, s[:-5].split('_'))) + output += f" {camelcase},{new_line}" + output += f"}}{new_line}{new_line}" +for (tags, enum_name) in zip(tag_list_list, enum_names): + output += f"impl {enum_name} {{{new_line}" + output += f" pub fn name(&self) -> &'static str {{{new_line}" + output += f" match self {{{new_line}" + for s in tags: + snakecase = s[:-5] + camelcase = ''.join(map(str.title, snakecase.split('_'))) + output += f" {enum_name}::{camelcase} => {quotes}{snakecase}{quotes},{new_line}" + output += " }\n }\n}\n" + output += f"impl From<{enum_name}> for &'static str {{{new_line}" + output += f" fn from(tag: {enum_name}) -> Self {{{new_line}" + output += " tag.name()\n" + output += " }\n}\n" +common.output("src/vanilla_tags.rs", output) \ No newline at end of file diff --git a/feather/generated/src/lib.rs b/feather/generated/src/lib.rs index ba9575b0a..01a61fa93 100644 --- a/feather/generated/src/lib.rs +++ b/feather/generated/src/lib.rs @@ -15,6 +15,8 @@ mod item; mod particle; #[allow(clippy::all)] mod simplified_block; +#[allow(clippy::all)] +pub mod vanilla_tags; pub use biome::Biome; pub use block::BlockKind; diff --git a/feather/generated/src/vanilla_tags.rs b/feather/generated/src/vanilla_tags.rs new file mode 100644 index 000000000..9c7706e9d --- /dev/null +++ b/feather/generated/src/vanilla_tags.rs @@ -0,0 +1,355 @@ +// This file is @generated. Please do not edit. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VanillaBlockTags { + AcaciaLogs, + Anvil, + BambooPlantableOn, + Banners, + BaseStoneNether, + BaseStoneOverworld, + BeaconBaseBlocks, + Beds, + Beehives, + BeeGrowables, + BirchLogs, + Buttons, + Campfires, + Carpets, + Climbable, + Corals, + CoralBlocks, + CoralPlants, + CrimsonStems, + Crops, + DarkOakLogs, + Doors, + DragonImmune, + EndermanHoldable, + Fences, + FenceGates, + Fire, + Flowers, + FlowerPots, + GoldOres, + GuardedByPiglins, + HoglinRepellents, + Ice, + Impermeable, + InfiniburnEnd, + InfiniburnNether, + InfiniburnOverworld, + JungleLogs, + Leaves, + Logs, + LogsThatBurn, + MushroomGrowBlock, + NonFlammableWood, + Nylium, + OakLogs, + PiglinRepellents, + Planks, + Portals, + PressurePlates, + PreventMobSpawningInside, + Rails, + Sand, + Saplings, + ShulkerBoxes, + Signs, + Slabs, + SmallFlowers, + SoulFireBaseBlocks, + SoulSpeedBlocks, + SpruceLogs, + Stairs, + StandingSigns, + StoneBricks, + StonePressurePlates, + StriderWarmBlocks, + TallFlowers, + Trapdoors, + UnderwaterBonemeals, + UnstableBottomCenter, + ValidSpawn, + Walls, + WallCorals, + WallPostOverride, + WallSigns, + WarpedStems, + WartBlocks, + WitherImmune, + WitherSummonBaseBlocks, + WoodenButtons, + WoodenDoors, + WoodenFences, + WoodenPressurePlates, + WoodenSlabs, + WoodenStairs, + WoodenTrapdoors, + Wool, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VanillaEntityTypes { + Arrows, + BeehiveInhabitors, + ImpactProjectiles, + Raiders, + Skeletons, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VanillaFluidTags { + Lava, + Water, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VanillaItemTags { + AcaciaLogs, + Anvil, + Arrows, + Banners, + BeaconPaymentItems, + Beds, + BirchLogs, + Boats, + Buttons, + Carpets, + Coals, + CreeperDropMusicDiscs, + CrimsonStems, + DarkOakLogs, + Doors, + Fences, + Fishes, + Flowers, + GoldOres, + JungleLogs, + Leaves, + LecternBooks, + Logs, + LogsThatBurn, + MusicDiscs, + NonFlammableWood, + OakLogs, + PiglinLoved, + PiglinRepellents, + Planks, + Rails, + Sand, + Saplings, + Signs, + Slabs, + SmallFlowers, + SoulFireBaseBlocks, + SpruceLogs, + Stairs, + StoneBricks, + StoneCraftingMaterials, + StoneToolMaterials, + TallFlowers, + Trapdoors, + Walls, + WarpedStems, + WoodenButtons, + WoodenDoors, + WoodenFences, + WoodenPressurePlates, + WoodenSlabs, + WoodenStairs, + WoodenTrapdoors, + Wool, +} + +impl VanillaBlockTags { + pub fn name(&self) -> &'static str { + match self { + VanillaBlockTags::AcaciaLogs => "acacia_logs", + VanillaBlockTags::Anvil => "anvil", + VanillaBlockTags::BambooPlantableOn => "bamboo_plantable_on", + VanillaBlockTags::Banners => "banners", + VanillaBlockTags::BaseStoneNether => "base_stone_nether", + VanillaBlockTags::BaseStoneOverworld => "base_stone_overworld", + VanillaBlockTags::BeaconBaseBlocks => "beacon_base_blocks", + VanillaBlockTags::Beds => "beds", + VanillaBlockTags::Beehives => "beehives", + VanillaBlockTags::BeeGrowables => "bee_growables", + VanillaBlockTags::BirchLogs => "birch_logs", + VanillaBlockTags::Buttons => "buttons", + VanillaBlockTags::Campfires => "campfires", + VanillaBlockTags::Carpets => "carpets", + VanillaBlockTags::Climbable => "climbable", + VanillaBlockTags::Corals => "corals", + VanillaBlockTags::CoralBlocks => "coral_blocks", + VanillaBlockTags::CoralPlants => "coral_plants", + VanillaBlockTags::CrimsonStems => "crimson_stems", + VanillaBlockTags::Crops => "crops", + VanillaBlockTags::DarkOakLogs => "dark_oak_logs", + VanillaBlockTags::Doors => "doors", + VanillaBlockTags::DragonImmune => "dragon_immune", + VanillaBlockTags::EndermanHoldable => "enderman_holdable", + VanillaBlockTags::Fences => "fences", + VanillaBlockTags::FenceGates => "fence_gates", + VanillaBlockTags::Fire => "fire", + VanillaBlockTags::Flowers => "flowers", + VanillaBlockTags::FlowerPots => "flower_pots", + VanillaBlockTags::GoldOres => "gold_ores", + VanillaBlockTags::GuardedByPiglins => "guarded_by_piglins", + VanillaBlockTags::HoglinRepellents => "hoglin_repellents", + VanillaBlockTags::Ice => "ice", + VanillaBlockTags::Impermeable => "impermeable", + VanillaBlockTags::InfiniburnEnd => "infiniburn_end", + VanillaBlockTags::InfiniburnNether => "infiniburn_nether", + VanillaBlockTags::InfiniburnOverworld => "infiniburn_overworld", + VanillaBlockTags::JungleLogs => "jungle_logs", + VanillaBlockTags::Leaves => "leaves", + VanillaBlockTags::Logs => "logs", + VanillaBlockTags::LogsThatBurn => "logs_that_burn", + VanillaBlockTags::MushroomGrowBlock => "mushroom_grow_block", + VanillaBlockTags::NonFlammableWood => "non_flammable_wood", + VanillaBlockTags::Nylium => "nylium", + VanillaBlockTags::OakLogs => "oak_logs", + VanillaBlockTags::PiglinRepellents => "piglin_repellents", + VanillaBlockTags::Planks => "planks", + VanillaBlockTags::Portals => "portals", + VanillaBlockTags::PressurePlates => "pressure_plates", + VanillaBlockTags::PreventMobSpawningInside => "prevent_mob_spawning_inside", + VanillaBlockTags::Rails => "rails", + VanillaBlockTags::Sand => "sand", + VanillaBlockTags::Saplings => "saplings", + VanillaBlockTags::ShulkerBoxes => "shulker_boxes", + VanillaBlockTags::Signs => "signs", + VanillaBlockTags::Slabs => "slabs", + VanillaBlockTags::SmallFlowers => "small_flowers", + VanillaBlockTags::SoulFireBaseBlocks => "soul_fire_base_blocks", + VanillaBlockTags::SoulSpeedBlocks => "soul_speed_blocks", + VanillaBlockTags::SpruceLogs => "spruce_logs", + VanillaBlockTags::Stairs => "stairs", + VanillaBlockTags::StandingSigns => "standing_signs", + VanillaBlockTags::StoneBricks => "stone_bricks", + VanillaBlockTags::StonePressurePlates => "stone_pressure_plates", + VanillaBlockTags::StriderWarmBlocks => "strider_warm_blocks", + VanillaBlockTags::TallFlowers => "tall_flowers", + VanillaBlockTags::Trapdoors => "trapdoors", + VanillaBlockTags::UnderwaterBonemeals => "underwater_bonemeals", + VanillaBlockTags::UnstableBottomCenter => "unstable_bottom_center", + VanillaBlockTags::ValidSpawn => "valid_spawn", + VanillaBlockTags::Walls => "walls", + VanillaBlockTags::WallCorals => "wall_corals", + VanillaBlockTags::WallPostOverride => "wall_post_override", + VanillaBlockTags::WallSigns => "wall_signs", + VanillaBlockTags::WarpedStems => "warped_stems", + VanillaBlockTags::WartBlocks => "wart_blocks", + VanillaBlockTags::WitherImmune => "wither_immune", + VanillaBlockTags::WitherSummonBaseBlocks => "wither_summon_base_blocks", + VanillaBlockTags::WoodenButtons => "wooden_buttons", + VanillaBlockTags::WoodenDoors => "wooden_doors", + VanillaBlockTags::WoodenFences => "wooden_fences", + VanillaBlockTags::WoodenPressurePlates => "wooden_pressure_plates", + VanillaBlockTags::WoodenSlabs => "wooden_slabs", + VanillaBlockTags::WoodenStairs => "wooden_stairs", + VanillaBlockTags::WoodenTrapdoors => "wooden_trapdoors", + VanillaBlockTags::Wool => "wool", + } + } +} +impl From for &'static str { + fn from(tag: VanillaBlockTags) -> Self { + tag.name() + } +} +impl VanillaEntityTypes { + pub fn name(&self) -> &'static str { + match self { + VanillaEntityTypes::Arrows => "arrows", + VanillaEntityTypes::BeehiveInhabitors => "beehive_inhabitors", + VanillaEntityTypes::ImpactProjectiles => "impact_projectiles", + VanillaEntityTypes::Raiders => "raiders", + VanillaEntityTypes::Skeletons => "skeletons", + } + } +} +impl From for &'static str { + fn from(tag: VanillaEntityTypes) -> Self { + tag.name() + } +} +impl VanillaFluidTags { + pub fn name(&self) -> &'static str { + match self { + VanillaFluidTags::Lava => "lava", + VanillaFluidTags::Water => "water", + } + } +} +impl From for &'static str { + fn from(tag: VanillaFluidTags) -> Self { + tag.name() + } +} +impl VanillaItemTags { + pub fn name(&self) -> &'static str { + match self { + VanillaItemTags::AcaciaLogs => "acacia_logs", + VanillaItemTags::Anvil => "anvil", + VanillaItemTags::Arrows => "arrows", + VanillaItemTags::Banners => "banners", + VanillaItemTags::BeaconPaymentItems => "beacon_payment_items", + VanillaItemTags::Beds => "beds", + VanillaItemTags::BirchLogs => "birch_logs", + VanillaItemTags::Boats => "boats", + VanillaItemTags::Buttons => "buttons", + VanillaItemTags::Carpets => "carpets", + VanillaItemTags::Coals => "coals", + VanillaItemTags::CreeperDropMusicDiscs => "creeper_drop_music_discs", + VanillaItemTags::CrimsonStems => "crimson_stems", + VanillaItemTags::DarkOakLogs => "dark_oak_logs", + VanillaItemTags::Doors => "doors", + VanillaItemTags::Fences => "fences", + VanillaItemTags::Fishes => "fishes", + VanillaItemTags::Flowers => "flowers", + VanillaItemTags::GoldOres => "gold_ores", + VanillaItemTags::JungleLogs => "jungle_logs", + VanillaItemTags::Leaves => "leaves", + VanillaItemTags::LecternBooks => "lectern_books", + VanillaItemTags::Logs => "logs", + VanillaItemTags::LogsThatBurn => "logs_that_burn", + VanillaItemTags::MusicDiscs => "music_discs", + VanillaItemTags::NonFlammableWood => "non_flammable_wood", + VanillaItemTags::OakLogs => "oak_logs", + VanillaItemTags::PiglinLoved => "piglin_loved", + VanillaItemTags::PiglinRepellents => "piglin_repellents", + VanillaItemTags::Planks => "planks", + VanillaItemTags::Rails => "rails", + VanillaItemTags::Sand => "sand", + VanillaItemTags::Saplings => "saplings", + VanillaItemTags::Signs => "signs", + VanillaItemTags::Slabs => "slabs", + VanillaItemTags::SmallFlowers => "small_flowers", + VanillaItemTags::SoulFireBaseBlocks => "soul_fire_base_blocks", + VanillaItemTags::SpruceLogs => "spruce_logs", + VanillaItemTags::Stairs => "stairs", + VanillaItemTags::StoneBricks => "stone_bricks", + VanillaItemTags::StoneCraftingMaterials => "stone_crafting_materials", + VanillaItemTags::StoneToolMaterials => "stone_tool_materials", + VanillaItemTags::TallFlowers => "tall_flowers", + VanillaItemTags::Trapdoors => "trapdoors", + VanillaItemTags::Walls => "walls", + VanillaItemTags::WarpedStems => "warped_stems", + VanillaItemTags::WoodenButtons => "wooden_buttons", + VanillaItemTags::WoodenDoors => "wooden_doors", + VanillaItemTags::WoodenFences => "wooden_fences", + VanillaItemTags::WoodenPressurePlates => "wooden_pressure_plates", + VanillaItemTags::WoodenSlabs => "wooden_slabs", + VanillaItemTags::WoodenStairs => "wooden_stairs", + VanillaItemTags::WoodenTrapdoors => "wooden_trapdoors", + VanillaItemTags::Wool => "wool", + } + } +} +impl From for &'static str { + fn from(tag: VanillaItemTags) -> Self { + tag.name() + } +} diff --git a/feather/server/Cargo.toml b/feather/server/Cargo.toml index 5a1f45a6e..94495869c 100644 --- a/feather/server/Cargo.toml +++ b/feather/server/Cargo.toml @@ -50,6 +50,7 @@ uuid = "0.8" vec-arena = "1" libcraft-core = { path = "../../libcraft/core" } worldgen = { path = "../worldgen", package = "feather-worldgen" } +datapacks = { path = "../datapacks", package = "feather-datapacks" } [features] default = [ "plugin-cranelift" ] diff --git a/feather/server/src/client.rs b/feather/server/src/client.rs index 96cdee503..edefb9c6e 100644 --- a/feather/server/src/client.rs +++ b/feather/server/src/client.rs @@ -178,7 +178,7 @@ impl Client { self.sent_entities.borrow().contains(&network_id) } - pub fn send_join_game(&self, gamemode: Gamemode) { + pub fn send_join_game(&self, gamemode: Gamemode, game: &common::Game) { log::trace!("Sending Join Game to {}", self.username); // Use the dimension codec sent by the default vanilla server. (Data acquired via tools/proxy) let dimension_codec = nbt::Blob::from_reader(&mut Cursor::new(include_bytes!( @@ -207,6 +207,8 @@ impl Client { is_debug: false, is_flat: false, }); + + self.send_packet(game.tag_registry.all_tags()); } pub fn send_brand(&self) { diff --git a/feather/server/src/main.rs b/feather/server/src/main.rs index 615857028..63eb13e06 100644 --- a/feather/server/src/main.rs +++ b/feather/server/src/main.rs @@ -38,6 +38,7 @@ async fn main() -> anyhow::Result<()> { fn init_game(server: Server, config: &Config) -> anyhow::Result { let mut game = Game::new(); + init_registries(&mut game)?; init_systems(&mut game, server); init_world_source(&mut game, config); init_plugin_manager(&mut game)?; @@ -60,9 +61,7 @@ fn init_systems(game: &mut Game, server: Server) { fn init_world_source(game: &mut Game, config: &Config) { // Load chunks from the world save first, - // and fall back to generating a superflat - // world otherwise. This is a placeholder: - // we don't have proper world generation yet. + // and fall back to generating a world otherwise. let seed = 42; // FIXME: load from the level file @@ -84,6 +83,18 @@ fn init_plugin_manager(game: &mut Game) -> anyhow::Result<()> { Ok(()) } +fn init_registries(game: &mut Game) -> anyhow::Result<()> { + game.tag_registry = datapacks::TagRegistryBuilder::from_dir( + std::path::Path::new("./feather/datapacks/minecraft/data/minecraft/tags"), + "minecraft", + )? + .build()?; + game.recipe_registry = datapacks::recipe::RecipeRegistry::from_dir(std::path::Path::new( + "./feather/datapacks/minecraft/data/minecraft/recipes", + ))?; + Ok(()) +} + fn print_systems(systems: &SystemExecutor) { let systems: Vec<&str> = systems.system_names().collect(); log::debug!("---SYSTEMS---\n{:#?}\n", systems); diff --git a/feather/server/src/systems/player_join.rs b/feather/server/src/systems/player_join.rs index a6d9d242e..ce6be4f4a 100644 --- a/feather/server/src/systems/player_join.rs +++ b/feather/server/src/systems/player_join.rs @@ -26,7 +26,7 @@ fn poll_new_players(game: &mut Game, server: &mut Server) -> SysResult { fn accept_new_player(game: &mut Game, server: &mut Server, client_id: ClientId) -> SysResult { let client = server.clients.get(client_id).unwrap(); - client.send_join_game(server.options.default_gamemode); + client.send_join_game(server.options.default_gamemode, game); client.send_brand(); let mut builder = game.create_entity_builder(Position::default(), EntityInit::Player);