From c2a7fd2ff12f599059ba794fbf6a23a246318ad6 Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 7 Apr 2021 11:49:11 +0200 Subject: [PATCH] Add enchantments and change data path resolution Signed-off-by: trivernis --- .gitignore | 1 + Cargo.toml | 4 +- src/api/enchantments.rs | 53 +++++++++++++++++++ src/api/mod.rs | 4 ++ src/api/tests/enchantments.rs | 47 +++++++++++++++++ src/api/tests/mod.rs | 1 + src/data/datapaths.rs | 9 ++++ src/data/mod.rs | 96 +++++++++++++++++++++-------------- src/models/enchantment.rs | 23 +++++++++ src/models/item.rs | 11 ++-- src/models/mod.rs | 1 + 11 files changed, 207 insertions(+), 43 deletions(-) create mode 100644 src/api/enchantments.rs create mode 100644 src/api/tests/enchantments.rs create mode 100644 src/data/datapaths.rs create mode 100644 src/models/enchantment.rs diff --git a/.gitignore b/.gitignore index 96ef6c0..408b8a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index debe82c..16d04a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minecraft-data-rs" -version = "0.1.0" +version = "0.2.0" authors = ["trivernis "] edition = "2018" readme = "README.md" @@ -16,3 +16,5 @@ serde_json = "1.0.64" serde_derive = "1.0.125" serde = "1.0.125" include_dir = "0.6.0" +itertools = "0.10.0" +lazy_static = "1.4.0" \ No newline at end of file diff --git a/src/api/enchantments.rs b/src/api/enchantments.rs new file mode 100644 index 0000000..6b006ac --- /dev/null +++ b/src/api/enchantments.rs @@ -0,0 +1,53 @@ +use crate::data::{get_version_specific_file, ENCHANTMENTS_FILE}; +use crate::models::enchantment::Enchantment; +use crate::models::version::Version; +use crate::{DataError, DataResult}; +use itertools::*; +use std::collections::HashMap; +use std::iter::FromIterator; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct Enchantments { + version: Arc, +} + +impl Enchantments { + pub fn new(version: Arc) -> Self { + Self { version } + } + + /// Returns a list of enchantments + pub fn enchantments_array(&self) -> DataResult> { + let content = get_version_specific_file(&self.version, ENCHANTMENTS_FILE)?; + + serde_json::from_str::>(&*content).map_err(DataError::from) + } + + /// Returns a map of enchantments indexed by ID + pub fn enchantments(&self) -> DataResult> { + Ok(HashMap::from_iter( + self.enchantments_array()?.into_iter().map(|e| (e.id, e)), + )) + } + + /// Returns a map of enchantments indexed by Name + pub fn enchantments_by_name(&self) -> DataResult> { + Ok(HashMap::from_iter( + self.enchantments_array()? + .into_iter() + .map(|e| (e.name.clone(), e)), + )) + } + + /// Returns enchantments grouped by category + pub fn enchantments_by_category(&self) -> DataResult>> { + Ok(HashMap::from_iter( + self.enchantments_array()? + .into_iter() + .group_by(|e| e.category.clone()) + .into_iter() + .map(|(key, group)| (key, group.collect())), + )) + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index c771c2b..6df3f64 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,4 @@ +use crate::api::enchantments::Enchantments; use crate::api::items::Items; use crate::api::recipes::Recipes; use crate::models::version::Version; @@ -6,6 +7,7 @@ use std::sync::Arc; #[cfg(test)] mod tests; +pub mod enchantments; pub mod items; mod recipes; pub mod versions; @@ -14,6 +16,7 @@ pub struct Api { pub version: Arc, pub items: Items, pub recipes: Recipes, + pub enchantments: Enchantments, } impl Api { @@ -23,6 +26,7 @@ impl Api { version: Arc::clone(&version), items: Items::new(Arc::clone(&version)), recipes: Recipes::new(Arc::clone(&version)), + enchantments: Enchantments::new(Arc::clone(&version)), } } } diff --git a/src/api/tests/enchantments.rs b/src/api/tests/enchantments.rs new file mode 100644 index 0000000..2cacd2c --- /dev/null +++ b/src/api/tests/enchantments.rs @@ -0,0 +1,47 @@ +use crate::api::tests::{get_api, get_test_versions}; + +#[test] +pub fn test_enchantments_array() { + let versions = get_test_versions(); + + for version in versions { + let api = get_api(version); + let enchantments_array = api.enchantments.enchantments_array().unwrap(); + assert_ne!(enchantments_array.len(), 0); + } +} + +#[test] +pub fn test_enchantments() { + let versions = get_test_versions(); + + for version in versions { + let api = get_api(version); + let enchantments = api.enchantments.enchantments().unwrap(); + assert_ne!(enchantments.len(), 0); + } +} + +#[test] +pub fn test_enchantments_by_name() { + let versions = get_test_versions(); + + for version in versions { + let api = get_api(version); + let by_name = api.enchantments.enchantments_by_name().unwrap(); + assert!(by_name.get("unbreaking").is_some()); + assert!(by_name.get("protection").is_some()); + } +} + +#[test] +pub fn test_enchantments_by_category() { + let versions = get_test_versions(); + + for version in versions { + let api = get_api(version); + let by_category = api.enchantments.enchantments_by_category().unwrap(); + assert!(by_category.get("breakable").is_some()); + assert_ne!(by_category.get("breakable").unwrap().len(), 0); + } +} diff --git a/src/api/tests/mod.rs b/src/api/tests/mod.rs index ec3f0c7..3f70346 100644 --- a/src/api/tests/mod.rs +++ b/src/api/tests/mod.rs @@ -2,6 +2,7 @@ use crate::api::versions::{available_versions, versions}; use crate::api::Api; use crate::models::version::Version; +mod enchantments; mod items; mod recipes; mod versions; diff --git a/src/data/datapaths.rs b/src/data/datapaths.rs new file mode 100644 index 0000000..dd7a032 --- /dev/null +++ b/src/data/datapaths.rs @@ -0,0 +1,9 @@ +use serde_json::Value; +use std::collections::HashMap; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub(crate) struct Datapaths { + pub pc: HashMap>, + pub pe: HashMap>, +} diff --git a/src/data/mod.rs b/src/data/mod.rs index ecddd53..48d4d46 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,31 +1,36 @@ +mod datapaths; + +use crate::data::datapaths::Datapaths; use crate::models::version::Version; use crate::{DataError, DataResult}; use include_dir::Dir; +use std::collections::HashMap; -pub static MINECRAFT_DATA: Dir = include_dir::include_dir!("minecraft-data/data/pc"); +pub static MINECRAFT_DATA: Dir = include_dir::include_dir!("minecraft-data/data"); -pub static BIOMES_FILE: &str = "biomes.json"; -pub static BLOCK_LOOT_FILE: &str = "blockLoot.json"; -pub static BLOCKS_FILE: &str = "blocks.json"; -pub static COMMANDS_FILE: &str = "commands.json"; -pub static ENTITIES_FILE: &str = "entities.json"; -pub static ENTITY_LOOT_FILE: &str = "entityLoot.json"; -pub static ITEMS_FILE: &str = "items.json"; -pub static LOGIN_PACKET_FILE: &str = "loginPacket.json"; -pub static MATERIALS_FILE: &str = "materials.json"; -pub static PROTOCOL_FILE: &str = "protocol.json"; -pub static RECIPES_FILE: &str = "recipes.json"; -pub static TINTS_FILE: &str = "tints.json"; +pub static BIOMES_FILE: &str = "biomes"; +pub static BLOCK_LOOT_FILE: &str = "blockLoot"; +pub static BLOCKS_FILE: &str = "blocks"; +pub static COMMANDS_FILE: &str = "commands"; +pub static ENTITIES_FILE: &str = "entities"; +pub static ENTITY_LOOT_FILE: &str = "entityLoot"; +pub static ITEMS_FILE: &str = "items"; +pub static LOGIN_PACKET_FILE: &str = "loginPacket"; +pub static MATERIALS_FILE: &str = "materials"; +pub static PROTOCOL_FILE: &str = "protocol"; +pub static RECIPES_FILE: &str = "recipes"; +pub static TINTS_FILE: &str = "tints"; +pub static ENCHANTMENTS_FILE: &str = "enchantments"; // pub static VERSION_FILE: &str = "version.json"; -pub static MAP_ICONS_FILE: &str = "mapIcons.json"; -pub static PARTICLES_FILE: &str = "particles.json"; -pub static PROTOCOL_VERSIONS_FILE: &str = "protocolVersions.json"; -pub static VERSIONS_FILE: &str = "versions.json"; +pub static MAP_ICONS_FILE: &str = "mapIcons"; +pub static PARTICLES_FILE: &str = "particles"; +pub static PROTOCOL_VERSIONS_FILE: &str = "protocolVersions"; +pub static VERSIONS_FILE: &str = "versions"; /// Returns the string encoded content of the common file pub fn get_common_file(filename: &str) -> DataResult { MINECRAFT_DATA - .get_file(format!("common/{}", filename)) + .get_file(format!("pc/common/{}.json", filename)) .ok_or(DataError::NotFoundError(filename.to_string()))? .contents_utf8() .ok_or(DataError::InvalidEncodingError(filename.to_string())) @@ -34,27 +39,40 @@ pub fn get_common_file(filename: &str) -> DataResult { /// Returns the string encoded content of the version specific file pub fn get_version_specific_file(version: &Version, filename: &str) -> DataResult { - let search_folders = vec![ - version.minecraft_version.clone(), - version.major_version.clone(), - version.major_first(), - version.previous_major(), - version.previous_major_first(), - ]; - let mut data = None; + let path = get_path(version, filename)?; + MINECRAFT_DATA + .get_file(format!("{}/{}.json", path, filename)) + .ok_or(DataError::NotFoundError(format!( + "{}/{}", + version.minecraft_version, filename + )))? + .contents_utf8() + .ok_or(DataError::InvalidEncodingError(filename.to_string())) + .map(|d| d.to_string()) +} - for folder in search_folders { - data = MINECRAFT_DATA.get_file(format!("{}/{}", folder, filename)); - if data.is_some() { - break; - } - } +/// Returns the data path for a given file +pub fn get_path(version: &Version, filename: &str) -> DataResult { + lazy_static::lazy_static! { + static ref PATHS: Datapaths = getDatapaths().unwrap(); + }; + PATHS + .pc + .get(&version.minecraft_version) + .ok_or(DataError::NotFoundError(version.minecraft_version.clone()))? + .get(filename) + .cloned() + .ok_or(DataError::NotFoundError(filename.to_string())) +} - data.ok_or(DataError::NotFoundError(format!( - "{}/{}", - version.minecraft_version, filename - )))? - .contents_utf8() - .ok_or(DataError::InvalidEncodingError(filename.to_string())) - .map(|d| d.to_string()) +/// Returns the parsed data paths +fn getDatapaths() -> DataResult { + let content = MINECRAFT_DATA + .get_file("dataPaths.json") + .ok_or(DataError::NotFoundError("dataPaths.json".to_string()))? + .contents_utf8() + .ok_or(DataError::InvalidEncodingError( + "dataPaths.json".to_string(), + ))?; + serde_json::from_str::(content).map_err(DataError::from) } diff --git a/src/models/enchantment.rs b/src/models/enchantment.rs new file mode 100644 index 0000000..5011e6a --- /dev/null +++ b/src/models/enchantment.rs @@ -0,0 +1,23 @@ +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub struct Enchantment { + pub id: u32, + pub name: String, + pub display_name: String, + pub max_level: u8, + pub min_cost: Cost, + pub max_cost: Cost, + pub treasure_only: bool, + pub exclude: Vec, + pub category: String, + pub weight: u8, + pub tradeable: bool, + pub discoverable: bool, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub struct Cost { + pub a: i32, + pub b: i32, +} diff --git a/src/models/item.rs b/src/models/item.rs index 933cde0..841ea6f 100644 --- a/src/models/item.rs +++ b/src/models/item.rs @@ -1,5 +1,3 @@ -use serde_json::Value; - #[derive(Deserialize, Debug, Clone)] #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] pub struct Item { @@ -10,6 +8,13 @@ pub struct Item { pub fixed_with: Option>, pub max_durability: Option, pub name: String, - pub variations: Option, + pub variations: Option>, pub durability: Option, } + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub struct Variation { + pub metadata: u32, + pub display_name: String, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 1a667d7..4d72f6a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,4 @@ +pub mod enchantment; pub mod item; pub mod recipe; pub mod version;