├── .github └── workflows │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src ├── api ├── biomes.rs ├── blocks.rs ├── enchantments.rs ├── entities.rs ├── foods.rs ├── items.rs ├── loot.rs ├── mod.rs ├── protocol.rs ├── recipes.rs ├── tests │ ├── biomes.rs │ ├── blocks.rs │ ├── enchantments.rs │ ├── entities.rs │ ├── foods.rs │ ├── items.rs │ ├── loot.rs │ ├── mod.rs │ ├── protocol.rs │ ├── recipes.rs │ └── versions.rs └── versions.rs ├── data ├── datapaths.rs └── mod.rs ├── lib.rs ├── models ├── biome.rs ├── block.rs ├── block_collision_shapes.rs ├── block_loot.rs ├── enchantment.rs ├── entity.rs ├── entity_loot.rs ├── food.rs ├── item.rs ├── mod.rs ├── protocol │ ├── mod.rs │ ├── packet_mapper.rs │ └── types.rs ├── recipe.rs └── version.rs └── utils ├── error.rs └── mod.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run checks and tests 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [ main, develop, feature/gh-actions ] 6 | pull_request: 7 | branches: [ main, develop ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | if: ${{ !env.ACT }} 18 | with: 19 | submodules: recursive 20 | 21 | - name: Cache build data 22 | if: ${{ !env.ACT }} 23 | uses: actions/cache@v2 24 | with: 25 | path: | 26 | target 27 | ~/.cargo/ 28 | key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-cargo- 31 | - name: Fetch 32 | run: cargo fetch 33 | 34 | - name: Check 35 | run: cargo check --all-features 36 | 37 | - name: Lint 38 | run: cargo clippy -- -D warnings 39 | 40 | - name: Test 41 | run : cargo test --all-features -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | jtd-codegen 5 | minecraft-data 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Before contributing to this repository, please discuss the changes you're willing to make via an issue before 4 | making those changes. 5 | 6 | 7 | ## Pull Request Process 8 | 9 | 1. If your pull request is a work in progress mark is as a draft. 10 | 2. Make sure that the project can be built and passes all automatic tests, especially: 11 | 1. `cargo test --lib` 12 | 2. `cargo clippy` 13 | 2. Your Pull Request will be merged after approval 14 | 15 | 16 | # Code of Conduct 17 | 18 | ## Our Pledge 19 | 20 | We as members, contributors, and leaders pledge to make participation in our 21 | community a harassment-free experience for everyone, regardless of age, body 22 | size, visible or invisible disability, ethnicity, sex characteristics, gender 23 | identity and expression, level of experience, education, socio-economic status, 24 | nationality, personal appearance, race, caste, color, religion, or sexual 25 | identity and orientation. 26 | 27 | We pledge to act and interact in ways that contribute to an open, welcoming, 28 | diverse, inclusive, and healthy community. 29 | 30 | ## Our Standards 31 | 32 | Examples of behavior that contributes to a positive environment for our 33 | community include: 34 | 35 | * Demonstrating empathy and kindness toward other people 36 | * Being respectful of differing opinions, viewpoints, and experiences 37 | * Giving and gracefully accepting constructive feedback 38 | * Accepting responsibility and apologizing to those affected by our mistakes, 39 | and learning from the experience 40 | * Focusing on what is best not just for us as individuals, but for the overall 41 | community 42 | 43 | Examples of unacceptable behavior include: 44 | 45 | * The use of sexualized language or imagery, and sexual attention or advances of 46 | any kind 47 | * Trolling, insulting or derogatory comments, and personal or political attacks 48 | * Public or private harassment 49 | * Publishing others' private information, such as a physical or email address, 50 | without their explicit permission 51 | * Other conduct which could reasonably be considered inappropriate in a 52 | professional setting 53 | 54 | ## Enforcement Responsibilities 55 | 56 | Community leaders are responsible for clarifying and enforcing our standards of 57 | acceptable behavior and will take appropriate and fair corrective action in 58 | response to any behavior that they deem inappropriate, threatening, offensive, 59 | or harmful. 60 | 61 | Community leaders have the right and responsibility to remove, edit, or reject 62 | comments, commits, code, wiki edits, issues, and other contributions that are 63 | not aligned to this Code of Conduct, and will communicate reasons for moderation 64 | decisions when appropriate. 65 | 66 | ## Scope 67 | 68 | This Code of Conduct applies within all community spaces, and also applies when 69 | an individual is officially representing the community in public spaces. 70 | Examples of representing our community include using an official e-mail address, 71 | posting via an official social media account, or acting as an appointed 72 | representative at an online or offline event. 73 | 74 | ## Enforcement 75 | 76 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 77 | reported to the community leaders responsible for enforcement at 78 | me@trivernis.net. 79 | All complaints will be reviewed and investigated promptly and fairly. 80 | 81 | All community leaders are obligated to respect the privacy and security of the 82 | reporter of any incident. 83 | 84 | ## Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 87 | version 2.1, available at 88 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). 89 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minecraft-data-rs" 3 | version = "0.8.1" 4 | authors = ["trivernis "] 5 | edition = "2021" 6 | readme = "README.md" 7 | license = "MIT" 8 | description = "A wrapper for minecraft-data" 9 | repository = "https://github.com/Trivernis/minecraft-data-rs" 10 | 11 | [package.metadata] 12 | minecraft_data_repo = "https://github.com/PrismarineJS/minecraft-data.git" 13 | minecraft_data_commit = "46b78398b6f351958e02fbd266424f0ee0ab138b" 14 | 15 | [dependencies] 16 | thiserror = "1.0.38" 17 | serde_json = "1.0.91" 18 | serde_derive = "1.0.151" 19 | serde = "1.0.151" 20 | include_dir = { version = "0.7.3", optional = true } 21 | itertools = { version = "0.10.5", optional = true } 22 | lazy_static = { version = "1.4.0", optional = true } 23 | 24 | [build-dependencies] 25 | git2 = {version = "0.18.2", optional = true} 26 | dirs = {version = "5.0.1", optional = true} 27 | cargo_toml = {version = "0.19.1", optional = true} 28 | serde = { version = "1.0.151", features = ["derive"], optional = true } 29 | 30 | [features] 31 | default = ["include-data", "api"] 32 | include-data = ["include_dir", "itertools", "lazy_static", "git2", "dirs", "cargo_toml", "serde/derive"] 33 | api = ["include-data"] 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 trivernis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minecraft-data-rs [![](https://img.shields.io/crates/v/minecraft-data-rs)](https://crates.io/crates/minecraft-data-rs) [![](https://img.shields.io/docsrs/minecraft-data-rs)](https://docs.rs/minecraft-data-rs) 2 | 3 | This repository is a rust library to access minecraft data. 4 | The data itself hosted in the [minecraft-data](https://github.com/PrismarineJS/minecraft-data) repository 5 | and included into the library at compile time. 6 | 7 | 8 | ### Excluding the minecraft-data at compile time 9 | By adding `default-features=false` to the dependency in your `Cargo.toml` file, you can exclude the minecraft-data from the library. 10 | 11 | 12 | ## Usage 13 | 14 | ```rust 15 | use std::collections::HashMap; 16 | use minecraft_data_rs::Api; 17 | use minecraft_data_rs::models::food::Food; 18 | use minecraft_data_rs::models::version::Version; 19 | 20 | // create an api wrapper for the latest stable version 21 | let api = Api::latest().expect("failed to retrieve latest version"); 22 | let food: Vec = api.foods.foods_array().unwrap(); 23 | 24 | for food in food { 25 | println!("When eating {} you gain {} food points", food.name, food.food_points); 26 | } 27 | ``` 28 | 29 | ## Features 30 | 31 | | Feature | Description | 32 | | -------------|------------------------------------------------------------| 33 | | include-data | includes the whole minecraft-data repository in the binary | 34 | | api | enables the api to query minecraft data | 35 | 36 | # License 37 | 38 | This project is Licensed under MIT. -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "include-data"))] 2 | fn main() {} 3 | 4 | #[cfg(feature = "include-data")] 5 | fn main() { 6 | data_repo::init_repo(); 7 | } 8 | 9 | #[cfg(feature = "include-data")] 10 | mod data_repo { 11 | use std::{env, fs, path::PathBuf}; 12 | 13 | use cargo_toml::Manifest; 14 | use git2::{Oid, Repository}; 15 | use serde::Deserialize; 16 | 17 | #[derive(Clone, Debug, Deserialize)] 18 | struct Metadata { 19 | minecraft_data_repo: String, 20 | minecraft_data_commit: String, 21 | } 22 | 23 | pub fn init_repo() { 24 | println!("cargo:rerun-if-env-changed=MINECRAFT_DATA_REPO_PATH"); 25 | println!("cargo:rerun-if-changed=Cargo.toml"); 26 | 27 | let manifest = Manifest::::from_path_with_metadata(PathBuf::from("Cargo.toml")) 28 | .expect("Failed to read manifest (Cargo.toml)"); 29 | let metadata = manifest 30 | .package 31 | .expect("missing package info in Cargo.toml") 32 | .metadata 33 | .expect("missing package.metadata in Cargo.toml"); 34 | 35 | let repo_path = env::var("MINECRAFT_DATA_REPO_PATH") 36 | .map(PathBuf::from) 37 | .ok() 38 | .or_else(|| dirs::cache_dir().map(|p| p.join("minecraft-data"))) 39 | .unwrap_or_else(|| PathBuf::from("minecraft-data")); 40 | 41 | println!( 42 | "cargo:rustc-env=MINECRAFT_DATA_PATH_INTERNAL={}", 43 | repo_path.to_string_lossy() 44 | ); 45 | 46 | let version_oid = Oid::from_str(&metadata.minecraft_data_commit).expect("invalid oid"); 47 | 48 | let repo = if repo_path.exists() { 49 | let repo = Repository::open(&repo_path).expect("failed to open git repo"); 50 | let head_oid = repo 51 | .head() 52 | .expect("no head found in repo") 53 | .peel_to_commit() 54 | .expect("head is not a commit") 55 | .as_object() 56 | .id(); 57 | if head_oid != version_oid { 58 | fs::remove_dir_all(&repo_path).expect("could not delete repository"); 59 | Repository::clone(&metadata.minecraft_data_repo, repo_path) 60 | .expect("failed to clone repo") 61 | } else { 62 | repo 63 | } 64 | } else { 65 | Repository::clone(&metadata.minecraft_data_repo, repo_path) 66 | .expect("failed to clone repo") 67 | }; 68 | 69 | repo.set_head_detached(version_oid) 70 | .expect("failed set head"); 71 | repo.checkout_head(None).expect("failed checkout index") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/api/biomes.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, BIOMES_FILE}; 2 | use crate::models::biome::Biome; 3 | use crate::models::version::Version; 4 | use crate::DataResult; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | pub struct Biomes { 9 | version: Arc, 10 | } 11 | 12 | impl Biomes { 13 | pub fn new(version: Arc) -> Self { 14 | Self { version } 15 | } 16 | 17 | /// Returns all biomes as an unordered list 18 | pub fn biomes_array(&self) -> DataResult> { 19 | let content = get_version_specific_file(&self.version, BIOMES_FILE)?; 20 | let biomes = serde_json::from_str(&content)?; 21 | 22 | Ok(biomes) 23 | } 24 | 25 | /// Returns the biomes indexed by id 26 | pub fn biomes(&self) -> DataResult> { 27 | let biomes = self.biomes_array()?; 28 | let biomes_map = biomes.into_iter().map(|b| (b.id, b)).collect(); 29 | 30 | Ok(biomes_map) 31 | } 32 | 33 | /// Returns the biomes indexed by name 34 | pub fn biomes_by_name(&self) -> DataResult> { 35 | let biomes = self.biomes_array()?; 36 | let biomes_map = biomes.into_iter().map(|b| (b.name.clone(), b)).collect(); 37 | 38 | Ok(biomes_map) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/blocks.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, BLOCKS_FILE, BLOCK_COLLISION_SHAPES_FILE}; 2 | use crate::models::block::Block; 3 | use crate::models::block_collision_shapes::BlockCollisionShapes; 4 | use crate::models::version::Version; 5 | use crate::DataResult; 6 | use std::collections::HashMap; 7 | use std::sync::Arc; 8 | 9 | pub struct Blocks { 10 | version: Arc, 11 | } 12 | 13 | impl Blocks { 14 | pub fn new(version: Arc) -> Self { 15 | Self { version } 16 | } 17 | 18 | /// Returns the list of blocks 19 | pub fn blocks_array(&self) -> DataResult> { 20 | let content = get_version_specific_file(&self.version, BLOCKS_FILE)?; 21 | let blocks = serde_json::from_str::>(&content)?; 22 | 23 | Ok(blocks) 24 | } 25 | 26 | // Returns the blocks indexed by state ID 27 | pub fn blocks_by_state_id(&self) -> DataResult> { 28 | let blocks = self.blocks_array()?; 29 | let mut blocks_map = HashMap::new(); 30 | blocks.iter().for_each(|b| { 31 | let min_state_id = b.min_state_id.unwrap_or(b.id << 4); 32 | let max_state_id = b.max_state_id.unwrap_or(min_state_id + 15); 33 | (min_state_id..=max_state_id).for_each(|s| { 34 | blocks_map.insert(s, b.clone()); 35 | }); 36 | }); 37 | 38 | Ok(blocks_map) 39 | } 40 | 41 | /// Returns the blocks indexed by name 42 | pub fn blocks_by_name(&self) -> DataResult> { 43 | let blocks = self.blocks_array()?; 44 | let blocks_map = blocks.into_iter().map(|b| (b.name.clone(), b)).collect(); 45 | 46 | Ok(blocks_map) 47 | } 48 | 49 | /// Returns the blocks indexed by ID 50 | pub fn blocks(&self) -> DataResult> { 51 | let blocks = self.blocks_array()?; 52 | let blocks_map = blocks.into_iter().map(|b| (b.id, b)).collect(); 53 | 54 | Ok(blocks_map) 55 | } 56 | 57 | /// Returns the block collision shapes object 58 | pub fn block_collision_shapes(&self) -> DataResult { 59 | let content = get_version_specific_file(&self.version, BLOCK_COLLISION_SHAPES_FILE)?; 60 | let collision_shapes = serde_json::from_str(&content)?; 61 | 62 | Ok(collision_shapes) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/api/enchantments.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, ENCHANTMENTS_FILE}; 2 | use crate::models::enchantment::Enchantment; 3 | use crate::models::version::Version; 4 | use crate::{DataError, DataResult}; 5 | use itertools::*; 6 | use std::collections::HashMap; 7 | use std::sync::Arc; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct Enchantments { 11 | version: Arc, 12 | } 13 | 14 | impl Enchantments { 15 | pub fn new(version: Arc) -> Self { 16 | Self { version } 17 | } 18 | 19 | /// Returns a list of enchantments 20 | pub fn enchantments_array(&self) -> DataResult> { 21 | let content = get_version_specific_file(&self.version, ENCHANTMENTS_FILE)?; 22 | 23 | serde_json::from_str::>(&content).map_err(DataError::from) 24 | } 25 | 26 | /// Returns a map of enchantments indexed by ID 27 | pub fn enchantments(&self) -> DataResult> { 28 | Ok(self 29 | .enchantments_array()? 30 | .into_iter() 31 | .map(|e| (e.id, e)) 32 | .collect()) 33 | } 34 | 35 | /// Returns a map of enchantments indexed by Name 36 | pub fn enchantments_by_name(&self) -> DataResult> { 37 | Ok(self 38 | .enchantments_array()? 39 | .into_iter() 40 | .map(|e| (e.name.clone(), e)) 41 | .collect()) 42 | } 43 | 44 | /// Returns enchantments grouped by category 45 | pub fn enchantments_by_category(&self) -> DataResult>> { 46 | Ok(self 47 | .enchantments_array()? 48 | .into_iter() 49 | .group_by(|e| e.category.clone()) 50 | .into_iter() 51 | .map(|(key, group)| (key, group.collect())) 52 | .collect()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/api/entities.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, ENTITIES_FILE}; 2 | use crate::models::entity::Entity; 3 | use crate::models::version::Version; 4 | use crate::DataResult; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | pub struct Entities { 9 | version: Arc, 10 | } 11 | 12 | impl Entities { 13 | pub fn new(version: Arc) -> Self { 14 | Self { version } 15 | } 16 | 17 | /// Returns an unordered array of entities 18 | pub fn entities_array(&self) -> DataResult> { 19 | let content = get_version_specific_file(&self.version, ENTITIES_FILE)?; 20 | let entities = serde_json::from_str(&content)?; 21 | 22 | Ok(entities) 23 | } 24 | 25 | /// Returns entities indexed by name 26 | pub fn entities_by_name(&self) -> DataResult> { 27 | let entities = self.entities_array()?; 28 | let entities_map = entities.into_iter().map(|e| (e.name.clone(), e)).collect(); 29 | 30 | Ok(entities_map) 31 | } 32 | 33 | /// Returns entities indexed by id 34 | pub fn entities(&self) -> DataResult> { 35 | let entities = self.entities_array()?; 36 | let entities_map = entities.into_iter().map(|e| (e.id, e)).collect(); 37 | 38 | Ok(entities_map) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/foods.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, FOODS_FILE}; 2 | use crate::models::food::Food; 3 | use crate::models::version::Version; 4 | use crate::DataResult; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | pub struct Foods { 9 | version: Arc, 10 | } 11 | 12 | impl Foods { 13 | pub fn new(version: Arc) -> Self { 14 | Self { version } 15 | } 16 | 17 | /// Returns the unindexed list of food 18 | pub fn foods_array(&self) -> DataResult> { 19 | let content = get_version_specific_file(&self.version, FOODS_FILE)?; 20 | let foods = serde_json::from_str(&content)?; 21 | 22 | Ok(foods) 23 | } 24 | 25 | /// Returns food indexed by id 26 | pub fn foods(&self) -> DataResult> { 27 | let foods = self.foods_array()?; 28 | let food_map = foods.into_iter().map(|f| (f.id, f)).collect(); 29 | 30 | Ok(food_map) 31 | } 32 | 33 | /// Returns food indexed by name 34 | pub fn foods_by_name(&self) -> DataResult> { 35 | let foods = self.foods_array()?; 36 | let food_map = foods.into_iter().map(|f| (f.name.clone(), f)).collect(); 37 | 38 | Ok(food_map) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/items.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, ITEMS_FILE}; 2 | use crate::models::item::Item; 3 | use crate::models::version::Version; 4 | use crate::{DataError, DataResult}; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | /// API to access item information 9 | pub struct Items { 10 | version: Arc, 11 | } 12 | 13 | impl Items { 14 | pub fn new(version: Arc) -> Self { 15 | Self { version } 16 | } 17 | 18 | /// Returns the items 19 | pub fn items_array(&self) -> DataResult> { 20 | let content = get_version_specific_file(&self.version, ITEMS_FILE)?; 21 | 22 | serde_json::from_str::>(&content).map_err(DataError::from) 23 | } 24 | 25 | /// Returns the items indexed by name 26 | pub fn items_by_name(&self) -> DataResult> { 27 | Ok(self 28 | .items_array()? 29 | .into_iter() 30 | .map(|i| (i.name.clone(), i)) 31 | .collect()) 32 | } 33 | 34 | /// Returns the items indexed by ID 35 | pub fn items(&self) -> DataResult> { 36 | Ok(self.items_array()?.into_iter().map(|i| (i.id, i)).collect()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/api/loot.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, BLOCK_LOOT_FILE, ENTITY_LOOT_FILE}; 2 | use crate::models::block_loot::BlockLoot; 3 | use crate::models::entity_loot::EntityLoot; 4 | use crate::models::version::Version; 5 | use crate::DataResult; 6 | use std::collections::HashMap; 7 | use std::sync::Arc; 8 | 9 | /// API to access item information 10 | pub struct Loot { 11 | version: Arc, 12 | } 13 | 14 | impl Loot { 15 | pub fn new(version: Arc) -> Self { 16 | Self { version } 17 | } 18 | 19 | /// Returns the entity loot in a list 20 | pub fn entity_loot_array(&self) -> DataResult> { 21 | let content = get_version_specific_file(&self.version, ENTITY_LOOT_FILE)?; 22 | let loot = serde_json::from_str::>(&content)?; 23 | 24 | Ok(loot) 25 | } 26 | 27 | /// Returns the entity loot indexed by entity name 28 | pub fn entity_loot(&self) -> DataResult> { 29 | let loot = self.entity_loot_array()?; 30 | let loot_map = loot.into_iter().map(|l| (l.entity.clone(), l)).collect(); 31 | 32 | Ok(loot_map) 33 | } 34 | 35 | /// Returns the block loot in a list 36 | pub fn block_loot_array(&self) -> DataResult> { 37 | let content = get_version_specific_file(&self.version, BLOCK_LOOT_FILE)?; 38 | let loot = serde_json::from_str::>(&content)?; 39 | 40 | Ok(loot) 41 | } 42 | 43 | /// Returns the block loot indexed by block name 44 | pub fn block_loot(&self) -> DataResult> { 45 | let loot = self.block_loot_array()?; 46 | let loot_map = loot.into_iter().map(|l| (l.block.clone(), l)).collect(); 47 | 48 | Ok(loot_map) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub use crate::api::biomes::Biomes; 2 | pub use crate::api::blocks::Blocks; 3 | pub use crate::api::enchantments::Enchantments; 4 | pub use crate::api::entities::Entities; 5 | pub use crate::api::foods::Foods; 6 | pub use crate::api::items::Items; 7 | pub use crate::api::loot::Loot; 8 | pub use crate::api::recipes::Recipes; 9 | use crate::models::version::Version; 10 | use crate::DataResult; 11 | use std::sync::Arc; 12 | 13 | #[cfg(test)] 14 | mod tests; 15 | 16 | mod biomes; 17 | mod blocks; 18 | mod enchantments; 19 | mod entities; 20 | mod foods; 21 | mod items; 22 | mod loot; 23 | mod protocol; 24 | mod recipes; 25 | mod versions; 26 | 27 | use crate::api::protocol::Protocol; 28 | pub use versions::*; 29 | 30 | /// A type wrapping access to all the metadata 31 | /// about the selected minecraft version 32 | #[allow(missing_docs)] 33 | pub struct Api { 34 | pub version: Arc, 35 | pub items: Items, 36 | pub recipes: Recipes, 37 | pub enchantments: Enchantments, 38 | pub loot: Loot, 39 | pub blocks: Blocks, 40 | pub foods: Foods, 41 | pub biomes: Biomes, 42 | pub entities: Entities, 43 | pub protocols: Protocol, 44 | } 45 | 46 | impl Api { 47 | /// Creates a new API wrapper for the latest version 48 | pub fn latest() -> DataResult { 49 | Ok(Self::new(latest_stable()?)) 50 | } 51 | 52 | /// Creates a new API wrapper for the provided version 53 | pub fn new(version: Version) -> Self { 54 | let version = Arc::new(version); 55 | Self { 56 | version: Arc::clone(&version), 57 | items: Items::new(Arc::clone(&version)), 58 | recipes: Recipes::new(Arc::clone(&version)), 59 | enchantments: Enchantments::new(Arc::clone(&version)), 60 | loot: Loot::new(Arc::clone(&version)), 61 | blocks: Blocks::new(Arc::clone(&version)), 62 | foods: Foods::new(Arc::clone(&version)), 63 | biomes: Biomes::new(Arc::clone(&version)), 64 | entities: Entities::new(Arc::clone(&version)), 65 | protocols: Protocol::new(Arc::clone(&version)), 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/api/protocol.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, PROTOCOL_FILE}; 2 | use crate::models::version::Version; 3 | use crate::{DataError, DataResult}; 4 | use std::sync::Arc; 5 | 6 | pub struct Protocol { 7 | version: Arc, 8 | } 9 | 10 | impl Protocol { 11 | pub fn new(version: Arc) -> Self { 12 | Self { version } 13 | } 14 | 15 | /// Returns the protocol information for the current version 16 | pub fn get_protocol(&self) -> DataResult { 17 | let content = get_version_specific_file(&self.version, PROTOCOL_FILE)?; 18 | serde_json::from_str(&content).map_err(DataError::from) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/api/recipes.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_version_specific_file, RECIPES_FILE}; 2 | use crate::models::recipe::Recipe; 3 | use crate::models::version::Version; 4 | use crate::{DataError, DataResult}; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct Recipes { 10 | version: Arc, 11 | } 12 | 13 | impl Recipes { 14 | pub fn new(version: Arc) -> Self { 15 | Self { version } 16 | } 17 | 18 | /// Returns a list of recipes indexed by item ID 19 | pub fn recipes(&self) -> DataResult>> { 20 | let content = get_version_specific_file(&self.version, RECIPES_FILE)?; 21 | serde_json::from_str::>>(&content).map_err(DataError::from) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/api/tests/biomes.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_biomes_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | assert_ne!(api.biomes.biomes_array().unwrap().len(), 0) 10 | } 11 | } 12 | 13 | #[test] 14 | pub fn test_biomes_by_name() { 15 | let versions = get_test_versions(); 16 | 17 | for version in versions { 18 | let api = get_api(version); 19 | let by_name = api.biomes.biomes_by_name().unwrap(); 20 | assert!(by_name.get("ocean").is_some()); 21 | assert!(by_name.get("river").is_some()); 22 | assert_eq!( 23 | by_name.get("jungle").unwrap().dimension, 24 | "overworld".to_string() 25 | ) 26 | } 27 | } 28 | 29 | #[test] 30 | pub fn test_biomes_by_id() { 31 | let versions = get_test_versions(); 32 | 33 | for version in versions { 34 | let api = get_api(version); 35 | let by_name = api.biomes.biomes().unwrap(); 36 | assert!(by_name.get(&1).is_some()); 37 | assert!(by_name.get(&5).is_some()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/api/tests/blocks.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | use crate::models::block_collision_shapes::CollisionShapeIds; 3 | 4 | #[test] 5 | pub fn test_blocks_array() { 6 | let versions = get_test_versions(); 7 | 8 | for version in versions { 9 | let api = get_api(version); 10 | assert_ne!(api.blocks.blocks_array().unwrap().len(), 0) 11 | } 12 | } 13 | 14 | #[test] 15 | pub fn test_blocks_by_state_id() { 16 | let versions = get_test_versions(); 17 | 18 | for version in versions { 19 | let api = get_api(version); 20 | let by_state = api.blocks.blocks_by_state_id(); 21 | assert!(by_state.is_ok()); 22 | } 23 | } 24 | 25 | #[test] 26 | pub fn test_blocks_by_name() { 27 | let versions = get_test_versions(); 28 | 29 | for version in versions { 30 | let api = get_api(version); 31 | let by_name = api.blocks.blocks_by_name().unwrap(); 32 | assert!(by_name.get("dirt").is_some()); 33 | assert!(by_name.get("stone").is_some()); 34 | assert_eq!(by_name.get("grass_block").unwrap().stack_size, 64) 35 | } 36 | } 37 | 38 | #[test] 39 | pub fn test_blocks_by_id() { 40 | let versions = get_test_versions(); 41 | 42 | for version in versions { 43 | let api = get_api(version); 44 | let by_id = api.blocks.blocks().unwrap(); 45 | assert!(by_id.get(&1).is_some()); 46 | assert!(by_id.get(&5).is_some()); 47 | } 48 | } 49 | 50 | #[test] 51 | pub fn test_block_states() { 52 | let versions = get_test_versions(); 53 | 54 | for version in versions { 55 | let api = get_api(version); 56 | let by_name = api.blocks.blocks_by_name().unwrap(); 57 | 58 | let air = by_name.get("air").unwrap(); 59 | if let Some(states) = &air.states { 60 | // Air has no states. 61 | assert_eq!(states.len(), 0); 62 | } 63 | 64 | let water = by_name.get("water").unwrap(); 65 | if let Some(states) = &water.states { 66 | // Water has states. 67 | assert_ne!(states.len(), 0); 68 | } 69 | } 70 | } 71 | 72 | #[test] 73 | pub fn test_block_collision_states() { 74 | for version in get_test_versions() { 75 | let api = get_api(version); 76 | let shapes = api.blocks.block_collision_shapes().unwrap(); 77 | 78 | for (_block, ids) in shapes.blocks { 79 | match ids { 80 | CollisionShapeIds::Value(id) => { 81 | assert!(shapes.shapes.get(&id).is_some()); 82 | } 83 | CollisionShapeIds::Array(ids) => { 84 | for id in ids { 85 | assert!(shapes.shapes.get(&id).is_some()); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/api/tests/enchantments.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_enchantments_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | let enchantments_array = api.enchantments.enchantments_array().unwrap(); 10 | assert_ne!(enchantments_array.len(), 0); 11 | } 12 | } 13 | 14 | #[test] 15 | pub fn test_enchantments() { 16 | let versions = get_test_versions(); 17 | 18 | for version in versions { 19 | let api = get_api(version); 20 | let enchantments = api.enchantments.enchantments().unwrap(); 21 | assert_ne!(enchantments.len(), 0); 22 | } 23 | } 24 | 25 | #[test] 26 | pub fn test_enchantments_by_name() { 27 | let versions = get_test_versions(); 28 | 29 | for version in versions { 30 | let api = get_api(version); 31 | let by_name = api.enchantments.enchantments_by_name().unwrap(); 32 | assert!(by_name.get("unbreaking").is_some()); 33 | assert!(by_name.get("protection").is_some()); 34 | } 35 | } 36 | 37 | #[test] 38 | pub fn test_enchantments_by_category() { 39 | let versions = get_test_versions(); 40 | 41 | for version in versions { 42 | let api = get_api(version); 43 | let by_category = api.enchantments.enchantments_by_category().unwrap(); 44 | assert!(by_category.get("breakable").is_some()); 45 | assert_ne!(by_category.get("breakable").unwrap().len(), 0); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/api/tests/entities.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_blocks_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | assert_ne!(api.entities.entities_array().unwrap().len(), 0) 10 | } 11 | } 12 | 13 | #[test] 14 | pub fn test_entities_by_name() { 15 | let versions = get_test_versions(); 16 | 17 | for version in versions { 18 | let api = get_api(version); 19 | let by_name = api.entities.entities_by_name().unwrap(); 20 | assert!(by_name.get("cow").is_some()); 21 | assert!(by_name.get("armor_stand").is_some()); 22 | } 23 | } 24 | 25 | #[test] 26 | pub fn test_entities_by_id() { 27 | let versions = get_test_versions(); 28 | 29 | for version in versions { 30 | let api = get_api(version); 31 | let by_name = api.entities.entities().unwrap(); 32 | assert!(by_name.get(&1).is_some()); 33 | assert!(by_name.get(&5).is_some()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/tests/foods.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_foods_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | assert_ne!(api.foods.foods_array().unwrap().len(), 0) 10 | } 11 | } 12 | 13 | #[test] 14 | pub fn test_foods_by_name() { 15 | let versions = get_test_versions(); 16 | 17 | for version in versions { 18 | let api = get_api(version); 19 | let by_name = api.foods.foods_by_name().unwrap(); 20 | assert!(by_name.get("bread").is_some()); 21 | assert!(by_name.get("golden_carrot").is_some()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/api/tests/items.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_items_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | assert_ne!(api.items.items_array().unwrap().len(), 0) 10 | } 11 | } 12 | 13 | #[test] 14 | pub fn test_items_by_name() { 15 | let versions = get_test_versions(); 16 | 17 | for version in versions { 18 | let api = get_api(version); 19 | let by_name = api.items.items_by_name().unwrap(); 20 | assert!(by_name.get("bread").is_some()); 21 | assert!(by_name.get("stone").is_some()); 22 | assert_eq!(by_name.get("ender_pearl").unwrap().stack_size, 16) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/tests/loot.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_entity_loot_array() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | assert_ne!(api.loot.entity_loot_array().unwrap().len(), 0) 10 | } 11 | } 12 | 13 | #[test] 14 | pub fn test_entity_loot_by_name() { 15 | let versions = get_test_versions(); 16 | 17 | for version in versions { 18 | let api = get_api(version); 19 | let by_name = api.loot.entity_loot().unwrap(); 20 | assert!(by_name.get("sheep").is_some()); 21 | assert!(by_name.get("zombie").is_some()); 22 | } 23 | } 24 | 25 | #[test] 26 | pub fn test_block_loot_array() { 27 | let versions = get_test_versions(); 28 | 29 | for version in versions { 30 | let api = get_api(version); 31 | assert_ne!(api.loot.block_loot_array().unwrap().len(), 0) 32 | } 33 | } 34 | 35 | #[test] 36 | pub fn test_block_loot_by_name() { 37 | let versions = get_test_versions(); 38 | 39 | for version in versions { 40 | let api = get_api(version); 41 | let by_name = api.loot.block_loot().unwrap(); 42 | assert!(by_name.get("poppy").is_some()); 43 | assert!(by_name.get("stone").is_some()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "api")] 2 | use crate::api::versions::{available_versions, versions}; 3 | use crate::api::Api; 4 | use crate::models::version::Version; 5 | 6 | mod biomes; 7 | mod blocks; 8 | mod enchantments; 9 | mod entities; 10 | mod foods; 11 | mod items; 12 | mod loot; 13 | mod protocol; 14 | mod recipes; 15 | mod versions; 16 | 17 | fn get_api(version: Version) -> Api { 18 | Api::new(version) 19 | } 20 | 21 | fn get_test_versions() -> Vec { 22 | let available = available_versions().unwrap(); 23 | versions() 24 | .unwrap() 25 | .into_iter() 26 | .filter(|v| available.contains(&v.minecraft_version)) 27 | .filter(|v| v.version >= 477) // datapaths < 1.14 are incomplete 28 | .collect() 29 | } 30 | -------------------------------------------------------------------------------- /src/api/tests/protocol.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::api::protocol::Protocol; 4 | use crate::api::tests::get_test_versions; 5 | 6 | #[test] 7 | pub fn test_get_protocol() { 8 | let versions = get_test_versions(); 9 | 10 | for x in versions { 11 | let arc = Arc::new(x); 12 | let protocol = Protocol::new(arc.clone()); 13 | 14 | if let Err(e) = protocol.get_protocol() { 15 | panic!( 16 | "Minecraft Version {} could not be parsed into a Protocol object: {}", 17 | arc.minecraft_version, e 18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/api/tests/recipes.rs: -------------------------------------------------------------------------------- 1 | use crate::api::tests::{get_api, get_test_versions}; 2 | 3 | #[test] 4 | pub fn test_recipes() { 5 | let versions = get_test_versions(); 6 | 7 | for version in versions { 8 | let api = get_api(version); 9 | let recipes = api.recipes.recipes().unwrap(); 10 | let bread_id = api.items.items_by_name().unwrap().get("bread").unwrap().id; 11 | assert_ne!(recipes.len(), 0); 12 | assert!(recipes.get(&bread_id).is_some()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/api/tests/versions.rs: -------------------------------------------------------------------------------- 1 | use crate::api::versions::{latest_stable, versions, versions_by_minecraft_version}; 2 | use crate::Api; 3 | 4 | #[test] 5 | fn test_versions() { 6 | let unordered_versions = versions().unwrap(); 7 | assert_ne!(unordered_versions.len(), 0) 8 | } 9 | 10 | #[test] 11 | fn test_versions_by_minecraft_version() { 12 | let versions = versions_by_minecraft_version().unwrap(); 13 | assert!(versions.get("1.16").is_some()); 14 | assert!(versions.get("1.14.12").is_none()); 15 | assert_eq!(versions.get("1.16.3").unwrap().major_version, "1.16") 16 | } 17 | 18 | #[test] 19 | fn test_latest_stable_version() { 20 | assert!(latest_stable().is_ok()); 21 | assert!(Api::latest().is_ok()); 22 | } 23 | -------------------------------------------------------------------------------- /src/api/versions.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{get_common_file, PROTOCOL_VERSIONS_FILE, VERSIONS_FILE}; 2 | use crate::models::version::Version; 3 | use crate::{DataError, DataResult}; 4 | use itertools::Itertools; 5 | use std::collections::HashMap; 6 | 7 | /// Returns the unsorted list of versions 8 | pub fn versions() -> DataResult> { 9 | let content = get_common_file(PROTOCOL_VERSIONS_FILE)?; 10 | let versions = serde_json::from_str::>(&content)?; 11 | 12 | Ok(versions) 13 | } 14 | 15 | /// Returns the versions indexed by minecraft version 16 | pub fn versions_by_minecraft_version() -> DataResult> { 17 | let indexed_versions = versions()? 18 | .into_iter() 19 | .map(|v| (v.minecraft_version.clone(), v)) 20 | .collect(); 21 | 22 | Ok(indexed_versions) 23 | } 24 | 25 | /// Returns the latest stable version for which data paths exists. 26 | /// Patch versions using the same data path as the major version are ignored. 27 | pub fn latest_stable() -> DataResult { 28 | let latest = available_versions()? 29 | .into_iter() 30 | .filter_map(|v| { 31 | let version_string = v.clone(); 32 | let mut parts = version_string.split('.'); 33 | 34 | Some(( 35 | v, 36 | parts.next()?.parse::().ok()?, 37 | parts.next()?.parse::().ok()?, 38 | parts.next().and_then(|p| p.parse::().ok()), 39 | )) 40 | }) 41 | .sorted_by_key(|(_, maj, min, patch)| { 42 | format!("{:#05}.{:#05}.{:#05}", maj, min, patch.unwrap_or(0)) 43 | }) 44 | .map(|(v, _, _, _)| v) 45 | .filter_map(|v| versions_by_minecraft_version().ok()?.remove(&v)) 46 | .next_back(); 47 | 48 | latest.ok_or_else(|| DataError::NotFoundError(String::from("latest version"))) 49 | } 50 | 51 | /// Returns a list of available version information 52 | pub fn available_versions() -> DataResult> { 53 | let content = get_common_file(VERSIONS_FILE)?; 54 | serde_json::from_str::>(&content).map_err(DataError::from) 55 | } 56 | -------------------------------------------------------------------------------- /src/data/datapaths.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | #[allow(unused)] 3 | #[derive(Deserialize, Debug, Clone)] 4 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 5 | pub(crate) struct Datapaths { 6 | pub pc: HashMap>, 7 | pub bedrock: HashMap>, 8 | } 9 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "api")] 2 | mod datapaths; 3 | 4 | use crate::data::datapaths::Datapaths; 5 | use crate::models::version::Version; 6 | use crate::{DataError, DataResult}; 7 | use include_dir::Dir; 8 | 9 | pub static MINECRAFT_DATA: Dir = include_dir::include_dir!("$MINECRAFT_DATA_PATH_INTERNAL/data"); 10 | 11 | pub static BIOMES_FILE: &str = "biomes"; 12 | pub static BLOCK_LOOT_FILE: &str = "blockLoot"; 13 | pub static BLOCKS_FILE: &str = "blocks"; 14 | pub static BLOCK_COLLISION_SHAPES_FILE: &str = "blockCollisionShapes"; 15 | #[allow(unused)] 16 | pub static COMMANDS_FILE: &str = "commands"; 17 | pub static ENTITIES_FILE: &str = "entities"; 18 | pub static ENTITY_LOOT_FILE: &str = "entityLoot"; 19 | pub static FOODS_FILE: &str = "foods"; 20 | pub static ITEMS_FILE: &str = "items"; 21 | #[allow(unused)] 22 | pub static LOGIN_PACKET_FILE: &str = "loginPacket"; 23 | #[allow(unused)] 24 | pub static MATERIALS_FILE: &str = "materials"; 25 | #[allow(unused)] 26 | pub static PROTOCOL_FILE: &str = "protocol"; 27 | pub static RECIPES_FILE: &str = "recipes"; 28 | #[allow(unused)] 29 | pub static TINTS_FILE: &str = "tints"; 30 | pub static ENCHANTMENTS_FILE: &str = "enchantments"; 31 | // pub static VERSION_FILE: &str = "version.json"; 32 | #[allow(unused)] 33 | pub static MAP_ICONS_FILE: &str = "mapIcons"; 34 | #[allow(unused)] 35 | pub static PARTICLES_FILE: &str = "particles"; 36 | pub static PROTOCOL_VERSIONS_FILE: &str = "protocolVersions"; 37 | #[allow(unused)] 38 | pub static VERSIONS_FILE: &str = "versions"; 39 | 40 | /// Returns the string encoded content of the common file 41 | pub fn get_common_file(filename: &str) -> DataResult { 42 | MINECRAFT_DATA 43 | .get_file(format!("pc/common/{}.json", filename)) 44 | .ok_or_else(|| DataError::NotFoundError(filename.to_string()))? 45 | .contents_utf8() 46 | .ok_or_else(|| DataError::InvalidEncodingError(filename.to_string())) 47 | .map(|d| d.to_string()) 48 | } 49 | 50 | /// Returns the string encoded content of the version specific file 51 | pub fn get_version_specific_file(version: &Version, filename: &str) -> DataResult { 52 | let path = get_path(version, filename)?; 53 | MINECRAFT_DATA 54 | .get_file(format!("{}/{}.json", path, filename)) 55 | .ok_or_else(|| { 56 | DataError::NotFoundError(format!("{}/{}", version.minecraft_version, filename)) 57 | })? 58 | .contents_utf8() 59 | .ok_or_else(|| DataError::InvalidEncodingError(filename.to_string())) 60 | .map(|d| d.to_string()) 61 | } 62 | 63 | /// Returns the data path for a given file 64 | pub fn get_path(version: &Version, filename: &str) -> DataResult { 65 | lazy_static::lazy_static! { 66 | static ref PATHS: Datapaths = get_datapaths().unwrap(); 67 | }; 68 | PATHS 69 | .pc 70 | .get(&version.minecraft_version) 71 | // fallback to major version 72 | .or_else(|| PATHS.pc.get(&version.major_version)) 73 | .ok_or_else(|| DataError::NotFoundError(version.minecraft_version.clone()))? 74 | .get(filename) 75 | .cloned() 76 | .ok_or_else(|| DataError::NotFoundError(filename.to_string())) 77 | } 78 | 79 | /// Returns the parsed data paths 80 | fn get_datapaths() -> DataResult { 81 | let content = MINECRAFT_DATA 82 | .get_file("dataPaths.json") 83 | .ok_or_else(|| DataError::NotFoundError("dataPaths.json".to_string()))? 84 | .contents_utf8() 85 | .ok_or_else(|| DataError::InvalidEncodingError("dataPaths.json".to_string()))?; 86 | serde_json::from_str::(content).map_err(DataError::from) 87 | } 88 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc=include_str!("../README.md")] 2 | 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | /// Provides data access methods 7 | #[cfg(feature = "api")] 8 | pub mod api; 9 | #[cfg(feature = "include-data")] 10 | pub(crate) mod data; 11 | /// Contains the type definitions for the data 12 | pub mod models; 13 | pub(crate) mod utils; 14 | 15 | #[cfg(feature = "api")] 16 | pub use api::Api; 17 | 18 | pub use utils::error::DataError; 19 | pub use utils::error::DataResult; 20 | -------------------------------------------------------------------------------- /src/models/biome.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct Biome { 4 | pub id: u32, 5 | pub name: String, 6 | pub category: String, 7 | pub temperature: f32, 8 | pub precipitation: Option, 9 | pub depth: Option, 10 | pub dimension: String, 11 | pub display_name: String, 12 | pub color: u32, 13 | pub rainfall: Option, 14 | } 15 | -------------------------------------------------------------------------------- /src/models/block.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Deserialize, Debug, Clone)] 4 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 5 | pub struct Block { 6 | pub id: u32, 7 | pub display_name: String, 8 | pub name: String, 9 | pub hardness: Option, 10 | pub stack_size: u8, 11 | pub diggable: bool, 12 | pub bounding_box: BoundingBox, 13 | pub material: Option, 14 | pub harvest_tools: Option>, 15 | pub variations: Option>, 16 | pub drops: Vec, 17 | pub transparent: bool, 18 | pub emit_light: u8, 19 | pub filter_light: u8, 20 | pub min_state_id: Option, 21 | pub max_state_id: Option, 22 | pub states: Option>, 23 | pub default_state: Option, 24 | #[serde(alias = "resistance")] 25 | pub blast_resistance: Option, 26 | } 27 | 28 | #[derive(Deserialize, Debug, Clone)] 29 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 30 | pub enum BoundingBox { 31 | Block, 32 | Empty, 33 | } 34 | 35 | #[derive(Deserialize, Debug, Clone)] 36 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 37 | pub struct Variation { 38 | pub metadata: u32, 39 | pub display_name: String, 40 | pub description: Option, 41 | } 42 | 43 | #[derive(Deserialize, Debug, Clone)] 44 | #[serde(untagged)] 45 | pub enum DropId { 46 | Id(u32), 47 | IdWithMetadata { id: u32, metadata: u32 }, 48 | } 49 | 50 | #[derive(Deserialize, Debug, Clone)] 51 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 52 | #[serde(untagged)] 53 | pub enum Drop { 54 | Id(u32), 55 | DropInfo { 56 | drop: DropId, 57 | min_count: Option, 58 | max_count: Option, 59 | } 60 | } 61 | 62 | #[derive(Deserialize, Debug, Clone)] 63 | // The fields in this part of the schema are not camelCase. 64 | #[serde(rename_all(serialize = "snake_case"))] 65 | pub struct State { 66 | pub name: String, 67 | #[serde(alias = "type")] 68 | pub state_type: StateType, 69 | pub values: Option>, 70 | pub num_values: u32, 71 | } 72 | 73 | #[derive(Deserialize, Debug, Clone)] 74 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 75 | pub enum StateType { 76 | Enum, 77 | Bool, 78 | Int, 79 | } 80 | -------------------------------------------------------------------------------- /src/models/block_collision_shapes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Deserialize, Debug, Clone)] 4 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 5 | pub struct BlockCollisionShapes { 6 | pub blocks: HashMap, 7 | pub shapes: HashMap, 8 | } 9 | 10 | #[derive(Deserialize, Debug, Clone)] 11 | #[serde( 12 | rename_all(deserialize = "camelCase", serialize = "snake_case"), 13 | untagged 14 | )] 15 | pub enum CollisionShapeIds { 16 | Value(u16), 17 | Array(Vec), 18 | } 19 | 20 | pub type CollisionShape = Vec>; 21 | -------------------------------------------------------------------------------- /src/models/block_loot.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct BlockLoot { 4 | pub block: String, 5 | pub drops: Vec, 6 | } 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 10 | pub struct ItemDrop { 11 | pub item: String, 12 | pub drop_chance: f32, 13 | pub stack_size_range: [Option; 2], 14 | pub block_age: Option, 15 | pub silk_touch: Option, 16 | pub no_silk_touch: Option, 17 | } 18 | -------------------------------------------------------------------------------- /src/models/enchantment.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct Enchantment { 4 | pub id: u32, 5 | pub name: String, 6 | pub display_name: String, 7 | pub max_level: u8, 8 | pub min_cost: Cost, 9 | pub max_cost: Cost, 10 | pub treasure_only: bool, 11 | pub exclude: Vec, 12 | pub category: String, 13 | pub weight: u8, 14 | pub tradeable: bool, 15 | pub discoverable: bool, 16 | } 17 | 18 | #[derive(Deserialize, Debug, Clone)] 19 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 20 | pub struct Cost { 21 | pub a: i32, 22 | pub b: i32, 23 | } 24 | -------------------------------------------------------------------------------- /src/models/entity.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct Entity { 4 | pub id: u32, 5 | pub internal_id: Option, 6 | pub display_name: String, 7 | pub name: String, 8 | #[serde(alias = "type")] 9 | pub entity_type: String, 10 | pub width: Option, 11 | pub height: Option, 12 | pub category: Option, 13 | } 14 | -------------------------------------------------------------------------------- /src/models/entity_loot.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct EntityLoot { 4 | pub entity: String, 5 | pub drops: Vec, 6 | } 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 10 | pub struct ItemDrop { 11 | pub item: String, 12 | pub drop_chance: f32, 13 | pub stack_size_range: [usize; 2], 14 | pub player_kill: Option, 15 | } 16 | -------------------------------------------------------------------------------- /src/models/food.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct Food { 4 | pub id: u32, 5 | pub display_name: String, 6 | pub stack_size: u8, 7 | pub name: String, 8 | pub food_points: f32, 9 | pub saturation: f32, 10 | pub effective_quality: f32, 11 | pub saturation_ratio: f32, 12 | pub variations: Option>, 13 | } 14 | 15 | #[derive(Deserialize, Debug, Clone)] 16 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 17 | pub struct Variation { 18 | pub metadata: u32, 19 | pub display_name: String, 20 | } 21 | -------------------------------------------------------------------------------- /src/models/item.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 3 | pub struct Item { 4 | pub id: u32, 5 | pub display_name: String, 6 | pub stack_size: u8, 7 | pub enchant_categories: Option>, 8 | pub fixed_with: Option>, 9 | pub max_durability: Option, 10 | pub name: String, 11 | pub variations: Option>, 12 | pub durability: Option, 13 | } 14 | 15 | #[derive(Deserialize, Debug, Clone)] 16 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 17 | pub struct Variation { 18 | pub metadata: u32, 19 | pub display_name: String, 20 | } 21 | -------------------------------------------------------------------------------- /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod biome; 2 | pub mod block; 3 | pub mod block_collision_shapes; 4 | pub mod block_loot; 5 | pub mod enchantment; 6 | pub mod entity; 7 | pub mod entity_loot; 8 | pub mod food; 9 | pub mod item; 10 | pub mod protocol; 11 | pub mod recipe; 12 | pub mod version; 13 | -------------------------------------------------------------------------------- /src/models/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod packet_mapper; 2 | pub mod types; 3 | 4 | pub use packet_mapper::{PacketMapper, PacketMapperSwitch, PacketSwitch}; 5 | use serde::de::{MapAccess, Visitor}; 6 | use serde::{de, Deserialize, Deserializer}; 7 | use serde_json::Value; 8 | use std::borrow::Cow; 9 | use std::fmt; 10 | pub use types::{BitField, NativeType, PacketDataType, PacketDataTypes}; 11 | 12 | #[derive(Deserialize, Debug, Clone)] 13 | pub struct Protocol { 14 | pub types: PacketDataTypes, 15 | pub handshaking: PacketGrouping, 16 | pub status: PacketGrouping, 17 | pub login: PacketGrouping, 18 | pub play: PacketGrouping, 19 | } 20 | 21 | #[derive(Deserialize, Debug, Clone)] 22 | pub struct PacketGrouping { 23 | #[serde(rename = "toServer")] 24 | pub to_server: PacketTypes, 25 | #[serde(rename = "toClient")] 26 | pub to_client: PacketTypes, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub enum DataTypeReference { 31 | Simple(String), 32 | Complex { name: String, properties: Value }, 33 | } 34 | 35 | impl From for PacketDataType { 36 | fn from(val: DataTypeReference) -> Self { 37 | let (name, properties) = match val { 38 | DataTypeReference::Simple(simple) => (simple, Value::Null), 39 | DataTypeReference::Complex { name, properties } => (name, properties), 40 | }; 41 | 42 | PacketDataType::new(name.as_str(), Cow::Borrowed(&properties)) 43 | .or_else(|| { 44 | let option = NativeType::new(name.as_str(), Cow::Borrowed(&properties)); 45 | option.map(PacketDataType::Native) 46 | }) 47 | .unwrap_or_else(|| PacketDataType::Other { 48 | name: Some(name.into()), 49 | value: properties, 50 | }) 51 | } 52 | } 53 | 54 | #[derive(Debug, Clone)] 55 | pub struct Packet { 56 | pub name: String, 57 | pub data: PacketDataType, 58 | } 59 | 60 | #[derive(Debug, Clone)] 61 | pub struct PacketTypes { 62 | pub packet_mapper: PacketMapperSwitch, 63 | pub types: Vec, 64 | } 65 | 66 | impl<'de> Deserialize<'de> for PacketTypes { 67 | fn deserialize(deserializer: D) -> Result 68 | where 69 | D: Deserializer<'de>, 70 | { 71 | struct PacketTypesVisitor; 72 | 73 | impl<'de> Visitor<'de> for PacketTypesVisitor { 74 | type Value = PacketTypes; 75 | 76 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 77 | formatter.write_str("Expected a map") 78 | } 79 | 80 | fn visit_map(self, mut map: A) -> Result 81 | where 82 | A: MapAccess<'de>, 83 | { 84 | while let Some(key) = map.next_key::()? { 85 | if key.eq("types") { 86 | let mut packets = Vec::new(); 87 | let mut packet_mapper = None; 88 | let value = map.next_value::()?; 89 | 90 | if let Value::Object(obj) = value { 91 | for (key, value) in obj.into_iter() { 92 | if key.eq("packet") { 93 | if let Value::Array(mut array) = value { 94 | let value = array.pop().ok_or_else(|| { 95 | de::Error::missing_field("missing content") 96 | })?; 97 | let value: PacketMapperSwitch = 98 | serde_json::from_value(value) 99 | .map_err(de::Error::custom)?; 100 | packet_mapper = Some(value); 101 | } else { 102 | return Err(de::Error::custom("Invalid Packet Mapper")); 103 | } 104 | } else if let Value::Array(array) = value { 105 | let value1 = Value::Array(vec![ 106 | Value::String(key.clone()), 107 | Value::Array(array), 108 | ]); 109 | let inner_type = types::build_inner_type(value1); 110 | packets.push(Packet { 111 | name: key, 112 | data: *inner_type, 113 | }); 114 | } else { 115 | return Err(de::Error::custom(format!( 116 | "Invalid Packet Expected Array {}", 117 | key 118 | ))); 119 | } 120 | } 121 | } 122 | 123 | let packet_mapper = packet_mapper 124 | .ok_or_else(|| de::Error::missing_field("packet_mapper"))?; 125 | return Ok(PacketTypes { 126 | packet_mapper, 127 | types: packets, 128 | }); 129 | } 130 | } 131 | Err(de::Error::custom("Expected a types")) 132 | } 133 | } 134 | 135 | deserializer.deserialize_map(PacketTypesVisitor) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/models/protocol/packet_mapper.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::convert::TryInto; 3 | use std::fmt; 4 | use std::num::ParseIntError; 5 | 6 | use serde::de::{SeqAccess, Visitor}; 7 | use serde::{de, Deserialize, Deserializer}; 8 | use serde_json::Value; 9 | 10 | #[derive(Deserialize, Debug, Clone)] 11 | pub struct PacketMapper { 12 | /// A Type 13 | #[serde(rename = "type")] 14 | pub map_type: String, 15 | /// The first Value is a Hex value of the packet id. That is a string in the JSON. You can convert the map with the `i32::from_str_radix` (The ids do start with 0x) function. You can also just do try_into::() on the PacketMapper 16 | /// The second Value is the name of the packet 17 | pub mappings: HashMap, 18 | } 19 | 20 | impl TryInto> for PacketMapper { 21 | type Error = ParseIntError; 22 | 23 | fn try_into(self) -> Result, Self::Error> { 24 | let mut map = HashMap::with_capacity(self.mappings.len()); 25 | for (key, value) in self.mappings.into_iter() { 26 | let key = i32::from_str_radix(key.trim_start_matches("0x"), 16)?; 27 | map.insert(key, value); 28 | } 29 | Ok(map) 30 | } 31 | } 32 | 33 | #[derive(Deserialize, Debug, Clone)] 34 | pub struct PacketSwitch { 35 | #[serde(rename = "compareTo")] 36 | pub compare_to: String, 37 | /// First value is the name of the packet. Second is the name of the JSON object for the packet. 38 | pub fields: HashMap, 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct PacketMapperSwitch { 43 | pub mapper: PacketMapper, 44 | pub switch: PacketSwitch, 45 | } 46 | 47 | impl<'de> Deserialize<'de> for PacketMapperSwitch { 48 | fn deserialize(deserializer: D) -> Result 49 | where 50 | D: Deserializer<'de>, 51 | { 52 | struct PacketMapperSwitchVisitor; 53 | 54 | impl<'de> Visitor<'de> for PacketMapperSwitchVisitor { 55 | type Value = PacketMapperSwitch; 56 | 57 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 58 | formatter.write_str("Expected a sequence") 59 | } 60 | fn visit_seq(self, mut seq: A) -> Result 61 | where 62 | A: SeqAccess<'de>, 63 | { 64 | let mut mapper = None; 65 | let mut switch = None; 66 | while let Some(value) = seq.next_element::()? { 67 | if let Value::Object(mut value) = value { 68 | let value = value 69 | .remove("type") 70 | .ok_or_else(|| de::Error::missing_field("type"))?; 71 | if let Value::Array(mut array) = value { 72 | let value = array 73 | .pop() 74 | .ok_or_else(|| de::Error::missing_field("missing content"))?; 75 | let key = array 76 | .pop() 77 | .ok_or_else(|| de::Error::missing_field("missing key"))?; 78 | if let Value::String(key) = key { 79 | if key.eq("mapper") { 80 | let value: PacketMapper = 81 | serde_json::from_value(value).map_err(de::Error::custom)?; 82 | mapper = Some(value); 83 | } else if key.eq("switch") { 84 | let value: PacketSwitch = 85 | serde_json::from_value(value).map_err(de::Error::custom)?; 86 | switch = Some(value); 87 | } else { 88 | return Err(de::Error::custom("unknown key")); 89 | } 90 | } else { 91 | return Err(de::Error::custom("unknown key")); 92 | } 93 | } else { 94 | return Err(de::Error::custom("Expected an array")); 95 | } 96 | } 97 | } 98 | let map_type = mapper.ok_or_else(|| de::Error::missing_field("mapper"))?; 99 | let switch = switch.ok_or_else(|| de::Error::missing_field("switch"))?; 100 | Ok(PacketMapperSwitch { 101 | mapper: map_type, 102 | switch, 103 | }) 104 | } 105 | } 106 | 107 | deserializer.deserialize_seq(PacketMapperSwitchVisitor) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/models/protocol/types.rs: -------------------------------------------------------------------------------- 1 | use serde::de::Visitor; 2 | use serde::{Deserialize, Deserializer}; 3 | use serde_json::Value; 4 | use std::borrow::Cow; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Deserialize, Debug, Clone)] 8 | pub struct BitField { 9 | pub name: String, 10 | pub size: i64, 11 | pub signed: bool, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub enum SwitchType { 16 | Packet(String), 17 | Type(Box), 18 | Unknown(Value), 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub enum TypeName { 23 | Anonymous, 24 | Named(String), 25 | } 26 | 27 | impl From for TypeName { 28 | fn from(value: String) -> Self { 29 | Self::Named(value) 30 | } 31 | } 32 | 33 | impl Display for TypeName { 34 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 35 | match self { 36 | TypeName::Anonymous => f.write_str("Anonymous"), 37 | TypeName::Named(name) => f.write_str(name.as_str()), 38 | } 39 | } 40 | } 41 | 42 | impl PartialEq for TypeName { 43 | fn eq(&self, other: &String) -> bool { 44 | if let TypeName::Named(name) = self { 45 | name == other 46 | } else { 47 | false 48 | } 49 | } 50 | } 51 | 52 | /// These data types should be available in every version. 53 | /// However, they won't break anything if not present 54 | /// This can also be known as the Native Types 55 | #[derive(Debug, Clone)] 56 | #[non_exhaustive] 57 | pub enum NativeType { 58 | /// Please read the following link for information on parsing https://wiki.vg/Protocol#VarInt_and_VarLong 59 | VarInt, 60 | PString { 61 | count_type: Box, 62 | }, 63 | Buffer { 64 | count_type: Box, 65 | }, 66 | Bool, 67 | U8, 68 | U16, 69 | U32, 70 | U64, 71 | I8, 72 | I16, 73 | I32, 74 | I64, 75 | F32, 76 | F64, 77 | Uuid, 78 | // Optional 79 | Option(Box), 80 | EntityMetadataLoop { 81 | end_val: i64, 82 | metadata_type: Box, 83 | }, 84 | TopBitSetTerminatedArray(Box), 85 | BitField(Vec), 86 | // A set of Name and The Type 87 | Container(Vec<(TypeName, Box)>), 88 | Switch { 89 | compare_to: String, 90 | fields: HashMap, 91 | default: Option, 92 | }, 93 | Void, 94 | Array { 95 | count_type: Box, 96 | array_type: Box, 97 | }, 98 | RestBuffer, 99 | NBT, 100 | OptionalNBT, 101 | } 102 | 103 | impl Display for NativeType { 104 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 105 | let value = match self { 106 | NativeType::Bool => "bool", 107 | NativeType::U8 => "u8", 108 | NativeType::U16 => "u16", 109 | NativeType::U32 => "u32", 110 | NativeType::U64 => "u64", 111 | NativeType::I8 => "i8", 112 | NativeType::I16 => "i16", 113 | NativeType::I32 => "i32", 114 | NativeType::I64 => "i64", 115 | NativeType::F32 => "f32", 116 | NativeType::F64 => "f64", 117 | NativeType::Uuid => "uuid", 118 | NativeType::Option(_) => "option", 119 | NativeType::EntityMetadataLoop { .. } => "entityMetadataLoop", 120 | NativeType::TopBitSetTerminatedArray(_) => "topbitsetterminatedarray", 121 | NativeType::BitField(_) => "bitfield", 122 | NativeType::Container(_) => "container", 123 | NativeType::Switch { .. } => "switch", 124 | NativeType::Array { .. } => "array", 125 | NativeType::Void => "void", 126 | NativeType::RestBuffer => "restbuffer", 127 | NativeType::NBT => "nbt", 128 | NativeType::OptionalNBT => "optionalnbt", 129 | NativeType::VarInt => "varint", 130 | NativeType::PString { .. } => "pstring", 131 | NativeType::Buffer { .. } => "buffer", 132 | }; 133 | write!(f, "{}", value) 134 | } 135 | } 136 | 137 | impl NativeType { 138 | pub fn contains_type(name: &str) -> bool { 139 | matches!( 140 | name, 141 | "varint" 142 | | "pstring" 143 | | "buffer" 144 | | "bool" 145 | | "u8" 146 | | "u16" 147 | | "u32" 148 | | "u64" 149 | | "i8" 150 | | "i16" 151 | | "i32" 152 | | "i64" 153 | | "f32" 154 | | "f64" 155 | | "uuid" 156 | | "option" 157 | | "entityMetadataLoop" 158 | | "topbitsetterminatedarray" 159 | | "bitfield" 160 | | "container" 161 | | "switch" 162 | | "void" 163 | | "array" 164 | | "restbuffer" 165 | | "nbt" 166 | | "optionalnbt" 167 | ) 168 | } 169 | pub fn new(name: &str, layout: Cow<'_, Value>) -> Option { 170 | match name { 171 | "varint" => Some(NativeType::VarInt), 172 | "pstring" => Self::new_pstring(layout), 173 | "buffer" => Self::new_buffer(layout), 174 | "bool" => Some(NativeType::Bool), 175 | "u8" => Some(NativeType::U8), 176 | "u16" => Some(NativeType::U16), 177 | "u32" => Some(NativeType::U32), 178 | "u64" => Some(NativeType::U64), 179 | "i8" => Some(NativeType::I8), 180 | "i16" => Some(NativeType::I16), 181 | "i32" => Some(NativeType::I32), 182 | "i64" => Some(NativeType::I64), 183 | "f32" => Some(NativeType::F32), 184 | "f64" => Some(NativeType::F64), 185 | "uuid" => Some(NativeType::Uuid), 186 | "option" => Some(NativeType::Option(build_inner_type(layout.into_owned()))), 187 | "entityMetadataLoop" => Self::new_entity_metadata_loop(layout), 188 | "topbitsetterminatedarray" => Self::new_top_bitsetterminated_array(layout), 189 | "bitfield" => Self::new_bitfield(layout), 190 | "container" => Self::new_container(layout), 191 | 192 | "switch" => Self::new_switch(layout), 193 | "void" => Some(NativeType::Void), 194 | "array" => Self::new_array(layout), 195 | 196 | "restbuffer" => Some(NativeType::RestBuffer), 197 | "nbt" => Some(NativeType::NBT), 198 | "optionalnbt" => Some(NativeType::OptionalNBT), 199 | _ => None, 200 | } 201 | } 202 | 203 | pub fn new_pstring(layout: Cow) -> Option { 204 | if let Value::Object(mut obj) = layout.into_owned() { 205 | if let Value::String(count_type) = obj.remove("countType").unwrap_or_default() { 206 | if let Some(count_type) = NativeType::new(&count_type, Cow::Owned(Value::Null)) { 207 | return Some(NativeType::PString { 208 | count_type: Box::new(count_type), 209 | }); 210 | } 211 | } 212 | } 213 | None 214 | } 215 | 216 | fn new_top_bitsetterminated_array(layout: Cow) -> Option { 217 | if let Value::Object(mut layout) = layout.into_owned() { 218 | let inner_type = layout.remove("type").unwrap_or_default(); 219 | let inner_type = build_inner_type(inner_type); 220 | return Some(NativeType::TopBitSetTerminatedArray(inner_type)); 221 | } 222 | None 223 | } 224 | 225 | pub fn new_bitfield(layout: Cow) -> Option { 226 | if let Value::Array(bit_fields) = layout.into_owned() { 227 | let bit_fields_vec = bit_fields 228 | .into_iter() 229 | .map(|v| serde_json::from_value(v).unwrap()) 230 | .collect(); 231 | 232 | Some(NativeType::BitField(bit_fields_vec)) 233 | } else { 234 | None 235 | } 236 | } 237 | 238 | pub fn new_switch(layout: Cow) -> Option { 239 | if let Value::Object(mut layout) = layout.into_owned() { 240 | return Some(NativeType::Switch { 241 | compare_to: layout 242 | .remove("compareTo") 243 | .unwrap() 244 | .as_str() 245 | .unwrap_or_default() 246 | .to_string(), 247 | fields: layout 248 | .remove("fields") 249 | .and_then(|v| { 250 | if let Value::Object(fields) = v { 251 | Some( 252 | fields 253 | .into_iter() 254 | .map(|(k, v)| { 255 | if let Value::String(value) = v { 256 | if value.starts_with("packet") { 257 | (k, SwitchType::Packet(value)) 258 | } else { 259 | ( 260 | k, 261 | SwitchType::Type(build_inner_type( 262 | Value::String(value), 263 | )), 264 | ) 265 | } 266 | } else if let Value::Array(array) = v { 267 | ( 268 | k, 269 | SwitchType::Type(build_inner_type(Value::Array( 270 | array, 271 | ))), 272 | ) 273 | } else { 274 | (k, SwitchType::Unknown(v)) 275 | } 276 | }) 277 | .collect(), 278 | ) 279 | } else { 280 | None 281 | } 282 | }) 283 | .unwrap_or_default(), 284 | default: layout 285 | .remove("default") 286 | .map(|v| v.as_str().unwrap_or_default().to_string()), 287 | }); 288 | } 289 | None 290 | } 291 | 292 | pub fn new_array(layout: Cow) -> Option { 293 | if let Value::Object(mut obj) = layout.into_owned() { 294 | let value = NativeType::new( 295 | obj.remove("countType") 296 | .unwrap_or_default() 297 | .as_str() 298 | .unwrap_or_default(), 299 | Cow::Owned(Value::Null), 300 | ).unwrap_or(NativeType::VarInt); 301 | let inner_type = build_inner_type(obj.remove("type").unwrap_or_default()); 302 | return Some(NativeType::Array { 303 | count_type: Box::new(value), 304 | array_type: inner_type, 305 | }); 306 | } 307 | None 308 | } 309 | 310 | pub fn new_container(layout: Cow) -> Option { 311 | if let Value::Array(containers) = layout.into_owned() { 312 | let containers_vec = containers 313 | .into_iter() 314 | .map(|v| { 315 | if let Value::Object(mut obj) = v { 316 | if let Some(name) = obj.remove("name") { 317 | let name = name.as_str().unwrap().to_string(); 318 | let inner_type = obj.remove("type").unwrap_or_default(); 319 | let inner_type = build_inner_type(inner_type); 320 | (TypeName::Named(name), inner_type) 321 | } else { 322 | let inner_type = obj.remove("type").unwrap_or_default(); 323 | let inner_type = build_inner_type(inner_type); 324 | (TypeName::Anonymous, inner_type) 325 | } 326 | } else { 327 | panic!("Container is not an object"); 328 | } 329 | }) 330 | .collect(); 331 | 332 | Some(NativeType::Container(containers_vec)) 333 | } else { 334 | None 335 | } 336 | } 337 | 338 | pub fn new_entity_metadata_loop(layout: Cow) -> Option { 339 | match layout.into_owned() { 340 | Value::Object(mut layout) => { 341 | let end_val = layout 342 | .remove("endVal") 343 | .and_then(|v| v.as_i64()) 344 | .unwrap_or_default(); 345 | let inner_type = layout.remove("type").unwrap_or_default(); 346 | let inner_type = build_inner_type(inner_type); 347 | Some(NativeType::EntityMetadataLoop { 348 | end_val, 349 | metadata_type: inner_type, 350 | }) 351 | } 352 | _ => None, 353 | } 354 | } 355 | 356 | pub fn new_buffer(layout: Cow) -> Option { 357 | if let Value::Object(mut obj) = layout.into_owned() { 358 | if let Value::String(count_type) = obj.remove("countType").unwrap_or_default() { 359 | if let Some(count_type) = NativeType::new(&count_type, Cow::Owned(Value::Null)) { 360 | return Some(NativeType::PString { 361 | count_type: Box::new(count_type), 362 | }); 363 | } 364 | } 365 | } 366 | None 367 | } 368 | } 369 | 370 | #[inline] 371 | pub(crate) fn build_inner_type(value: Value) -> Box { 372 | match value { 373 | Value::String(simple_type) => { 374 | return if let Some(simple_type) = NativeType::new(&simple_type, Cow::Owned(Value::Null)) 375 | { 376 | Box::new(PacketDataType::Native(simple_type)) 377 | } else { 378 | // Probably a reference to a built type 379 | Box::new(PacketDataType::Other { 380 | name: Some(simple_type.into()), 381 | value: Value::Null, 382 | }) 383 | }; 384 | } 385 | Value::Array(mut array) => { 386 | if array.len() != 2 { 387 | return Box::new(PacketDataType::Other { 388 | name: None, 389 | value: Value::Array(array), 390 | }); 391 | } 392 | let inner_value = Cow::Owned(array.pop().unwrap_or_default()); 393 | let key = array.pop().unwrap(); 394 | if let Value::String(key) = key { 395 | let value = PacketDataType::new(&key, Cow::clone(&inner_value)).or_else(|| { 396 | let option = NativeType::new(&key, inner_value.clone()); 397 | option.map(PacketDataType::Native) 398 | }); 399 | if let Some(value) = value { 400 | Box::new(value) 401 | } else { 402 | Box::new(PacketDataType::Other { 403 | name: Some(key.into()), 404 | value: inner_value.into_owned(), 405 | }) 406 | } 407 | } else { 408 | Box::new(PacketDataType::Other { 409 | name: None, 410 | value: Value::Array(vec![key, inner_value.into_owned()]), 411 | }) 412 | } 413 | } 414 | v => Box::new(PacketDataType::Other { 415 | name: None, 416 | value: v, 417 | }), 418 | } 419 | } 420 | 421 | #[derive(Debug, Clone)] 422 | pub enum PacketDataType { 423 | // Just a pure native type 424 | Native(NativeType), 425 | // It was marked as "native" however, this enum does not have it 426 | UnknownNativeType(String), 427 | // This type is built from a native type 428 | Built { 429 | // The name of the built type 430 | name: TypeName, 431 | // The value of the built type 432 | value: NativeType, 433 | }, 434 | /// A Type that could not be built or parsed. This is a fallback for when we don't know what the type is 435 | /// This type is usually a reference to a built type 436 | /// 437 | /// If this is a reference you you will want to push any data within Value to the reference. 438 | /// For example the Packet type references another type called "PacketData" that has a variable within it for the compareTo value. You will want to take the value from the Value and push it to the PacketData type. 439 | Other { 440 | // The name of the type if found 441 | name: Option, 442 | // The JSON value of the type 443 | value: Value, 444 | }, 445 | } 446 | 447 | impl From for PacketDataType { 448 | fn from(native: NativeType) -> Self { 449 | PacketDataType::Native(native) 450 | } 451 | } 452 | 453 | impl PacketDataType { 454 | pub fn new(key: &str, value: Cow<'_, Value>) -> Option { 455 | if !NativeType::contains_type(key) { 456 | match value.into_owned() { 457 | Value::String(string) => Some(PacketDataType::UnknownNativeType(string)), 458 | Value::Array(mut array) => { 459 | if array.len() != 2 { 460 | return Some(PacketDataType::Other { 461 | name: Some(key.to_string().into()), 462 | value: Value::Array(array), 463 | }); 464 | } 465 | 466 | let inner_type_values = array.pop().unwrap_or_default(); 467 | let inner_type_name = array.pop().unwrap(); 468 | if let Value::String(inner_type_name) = inner_type_name { 469 | return if let Some(type_) = 470 | NativeType::new(&inner_type_name, Cow::Borrowed(&inner_type_values)) 471 | { 472 | Some(PacketDataType::Built { 473 | name: TypeName::Named(key.to_string()), 474 | value: type_, 475 | }) 476 | } else { 477 | Some(PacketDataType::Other { 478 | name: Some(inner_type_name.into()), 479 | value: inner_type_values, 480 | }) 481 | }; 482 | } 483 | None 484 | } 485 | v => Some(PacketDataType::Other { 486 | name: Some(key.to_string().into()), 487 | value: v, 488 | }), 489 | } 490 | } else { 491 | None 492 | } 493 | } 494 | } 495 | 496 | #[derive(Debug, Clone)] 497 | pub struct PacketDataTypes { 498 | pub types: Vec, 499 | } 500 | 501 | use std::fmt; 502 | use std::fmt::{Debug, Display, Formatter}; 503 | 504 | use serde::de::MapAccess; 505 | 506 | impl<'de> Deserialize<'de> for PacketDataTypes { 507 | fn deserialize(deserializer: D) -> Result 508 | where 509 | D: Deserializer<'de>, 510 | { 511 | struct PacketDataTypesVisitor; 512 | 513 | impl<'de> Visitor<'de> for PacketDataTypesVisitor { 514 | type Value = PacketDataTypes; 515 | 516 | fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 517 | formatter.write_str("struct PacketDataTypes") 518 | } 519 | 520 | fn visit_map(self, mut map: V) -> Result 521 | where 522 | V: MapAccess<'de>, 523 | { 524 | let mut types = Vec::new(); 525 | while let Some(key) = map.next_key::()? { 526 | let value = map.next_value::()?; 527 | if let Some(ty) = PacketDataType::new(&key, Cow::Owned(value)) { 528 | types.push(ty); 529 | } 530 | } 531 | Ok(PacketDataTypes { types }) 532 | } 533 | } 534 | 535 | deserializer.deserialize_map(PacketDataTypesVisitor) 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/models/recipe.rs: -------------------------------------------------------------------------------- 1 | #[derive(Deserialize, Debug, Clone)] 2 | #[serde( 3 | rename_all(deserialize = "camelCase", serialize = "snake_case"), 4 | untagged 5 | )] 6 | pub enum Recipe { 7 | Shaped(ShapedRecipe), 8 | Shapeless(ShapelessRecipe), 9 | } 10 | 11 | #[derive(Deserialize, Debug, Clone)] 12 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 13 | pub struct ShapedRecipe { 14 | pub result: RecipeItem, 15 | pub in_shape: Shape, 16 | pub out_shape: Option, 17 | } 18 | 19 | #[derive(Deserialize, Debug, Clone)] 20 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 21 | pub struct ShapelessRecipe { 22 | pub result: RecipeItem, 23 | pub ingredients: Vec, 24 | } 25 | 26 | pub type Shape = Vec>; 27 | 28 | #[derive(Deserialize, Debug, Clone)] 29 | #[serde( 30 | rename_all(deserialize = "camelCase", serialize = "snake_case"), 31 | untagged 32 | )] 33 | pub enum RecipeItem { 34 | ID(u32), 35 | IDMetadataArray([u32; 2]), 36 | IDMetadataCountObject(IDMetadataCountObject), 37 | Null(Option<()>), 38 | } 39 | 40 | #[derive(Deserialize, Debug, Clone)] 41 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 42 | pub struct IDMetadataCountObject { 43 | pub id: i32, 44 | pub metadata: Option, 45 | pub count: Option, 46 | } 47 | -------------------------------------------------------------------------------- /src/models/version.rs: -------------------------------------------------------------------------------- 1 | /// A type wrapping a minecraft version 2 | #[derive(Deserialize, Debug, Clone)] 3 | #[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] 4 | pub struct Version { 5 | /// API Version 6 | pub version: i32, 7 | /// The full minecraft version (e.g. (`1.8.1`) 8 | pub minecraft_version: String, 9 | /// The major version of this minecraft version (e.g. `1.8`) 10 | pub major_version: String, 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | #[allow(missing_docs)] 5 | pub type DataResult = Result; 6 | 7 | #[derive(Error, Debug)] 8 | #[allow(missing_docs)] 9 | pub enum DataError { 10 | #[error("IO Error: {0}")] 11 | IOError(#[from] io::Error), 12 | 13 | #[error("JSON Error: {0}")] 14 | JsonError(#[from] serde_json::Error), 15 | 16 | #[error("Object {0} not found")] 17 | NotFoundError(String), 18 | 19 | #[error("Invalid encoding of file {0}")] 20 | InvalidEncodingError(String), 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | --------------------------------------------------------------------------------