minecraft_assets/api/
asset_pack.rs

1use std::{ops::Deref, path::Path};
2
3use serde::de::DeserializeOwned;
4
5use crate::{
6    api::{
7        FileSystemResourceProvider, ModelIdentifier, ResourceLocation, ResourceProvider, Result,
8    },
9    schemas::{BlockStates, Model},
10};
11
12/// Top-level API for accessing Minecraft assets.
13pub struct AssetPack {
14    provider: Box<dyn ResourceProvider>,
15}
16
17impl AssetPack {
18    /// Returns a new [`AssetPack`] that can read data from the given directory.
19    ///
20    /// The provided `root_dir` should be the directory that contains the
21    /// `assets/` and/or `data/` directories.
22    ///
23    /// # Example
24    ///
25    /// ```no_run
26    /// use minecraft_assets::api::AssetPack;
27    ///
28    /// let assets = AssetPack::at_path("~/.minecraft/");
29    ///
30    /// // Load the block states for `oak_planks`
31    /// let states = assets.load_blockstates("oak_planks").unwrap();
32    /// let variants = states.variants().unwrap();
33    ///
34    /// assert_eq!(variants.len(), 1);
35    ///
36    /// let model_properties = &variants[""].models()[0];
37    /// assert_eq!(model_properties.model, "block/oak_planks");
38    /// ```
39    pub fn at_path(root_dir: impl AsRef<Path>) -> Self {
40        let provider = FileSystemResourceProvider::new(root_dir);
41        Self {
42            provider: Box::new(provider),
43        }
44    }
45
46    /// Returns a new [`AssetPack`] that uses the given [`ResourceProvider`].
47    pub fn new<P>(provider: P) -> Self
48    where
49        P: ResourceProvider + 'static,
50    {
51        Self {
52            provider: Box::new(provider),
53        }
54    }
55
56    /// Loads the [`BlockStates`] of the block with the provided id.
57    ///
58    /// # Example
59    ///
60    /// ```no_run
61    /// # use minecraft_assets::api::*;
62    /// # let assets = AssetPack::at_path("foo");
63    /// let states = assets.load_blockstates("stone");
64    /// let states = assets.load_blockstates("minecraft:dirt");
65    /// ```
66    pub fn load_blockstates(&self, block_id: &str) -> Result<BlockStates> {
67        self.load_resource(&ResourceLocation::blockstates(block_id))
68    }
69
70    /// Loads the block [`Model`] identified by the given name or path.
71    ///
72    /// # Example
73    ///
74    /// ```no_run
75    /// # use minecraft_assets::api::*;
76    /// # let assets = AssetPack::at_path("foo");
77    /// let model = assets.load_block_model("stone");
78    /// let model = assets.load_block_model("block/dirt");
79    /// ```
80    pub fn load_block_model(&self, model: &str) -> Result<Model> {
81        self.load_resource(&ResourceLocation::block_model(model))
82    }
83
84    /// Loads the block [`Model`] identified by the given name or path, as well
85    /// as all of its parents and ancestors.
86    ///
87    /// The models are returned as a list, with the first element being the
88    /// model that was originally requested, the next element being its parent,
89    /// and so on with the last element being the topmost parent.
90    ///
91    /// # Example
92    ///
93    /// ```no_run
94    /// # use minecraft_assets::api::*;
95    /// # let assets = AssetPack::at_path("foo");
96    /// let models = assets.load_block_model_recursive("block/cube_all").unwrap();
97    ///
98    /// let expected = vec![
99    ///     assets.load_block_model("block/cube_all").unwrap(),
100    ///     assets.load_block_model("block/cube").unwrap(),
101    ///     assets.load_block_model("block/block").unwrap(),
102    /// ];
103    /// assert_eq!(models, expected);
104    /// ```
105    pub fn load_block_model_recursive(&self, model: &str) -> Result<Vec<Model>> {
106        self.load_model_recursive(&ResourceLocation::block_model(model))
107    }
108
109    /// Loads the item [`Model`] identified by the given name or path.
110    ///
111    /// # Example
112    ///
113    /// ```no_run
114    /// # use minecraft_assets::api::*;
115    /// # let assets = AssetPack::at_path("foo");
116    /// let model = assets.load_item_model("compass");
117    /// let model = assets.load_item_model("item/diamond_hoe");
118    /// ```
119    pub fn load_item_model(&self, model: &str) -> Result<Model> {
120        self.load_resource(&ResourceLocation::item_model(model))
121    }
122
123    /// Loads the item [`Model`] identified by the given name or path, as well
124    /// as all of its parents and ancestors.
125    ///
126    /// The models are returned as a list, with the first element being the
127    /// model that was originally requested, the next element being its parent,
128    /// and so on with the last element being the topmost parent.
129    ///
130    /// # Example
131    ///
132    /// ```no_run
133    /// # use minecraft_assets::api::*;
134    /// # let assets = AssetPack::at_path("foo");
135    /// let models = assets.load_item_model_recursive("item/diamond_hoe").unwrap();
136    ///
137    /// let expected = vec![
138    ///     assets.load_item_model("item/diamond_hoe").unwrap(),
139    ///     assets.load_item_model("item/handheld").unwrap(),
140    ///     assets.load_item_model("item/generated").unwrap(),
141    /// ];
142    /// assert_eq!(models, expected);
143    /// ```
144    pub fn load_item_model_recursive(&self, model: &str) -> Result<Vec<Model>> {
145        self.load_model_recursive(&ResourceLocation::item_model(model))
146    }
147
148    fn load_resource<T>(&self, resource: &ResourceLocation) -> Result<T>
149    where
150        T: DeserializeOwned,
151    {
152        let bytes = self.provider.load_resource(resource)?;
153        Ok(serde_json::from_reader(&bytes[..])?)
154    }
155
156    fn load_model_recursive(&self, resource: &ResourceLocation) -> Result<Vec<Model>> {
157        let mut models = Vec::new();
158
159        Self::for_each_parent(
160            resource.clone(),
161            |model| models.push(model),
162            |next_location| self.load_resource(next_location),
163        )?;
164
165        Ok(models)
166    }
167
168    pub(crate) fn for_each_parent<F, L, E>(
169        mut current: ResourceLocation,
170        mut op: F,
171        mut load_model: L,
172    ) -> Result<(), E>
173    where
174        F: FnMut(Model),
175        L: FnMut(&ResourceLocation) -> Result<Model, E>,
176    {
177        loop {
178            let model = load_model(&current)?;
179
180            let parent_owned = model.parent.clone();
181
182            op(model);
183
184            match parent_owned {
185                Some(parent) if !ModelIdentifier::is_builtin(&parent) => {
186                    //println!("{}", parent.as_str());
187                    current = ResourceLocation::new_owned(current.kind, parent);
188                }
189                _ => break,
190            }
191        }
192
193        Ok(())
194    }
195}
196
197impl Deref for AssetPack {
198    type Target = dyn ResourceProvider;
199
200    fn deref(&self) -> &Self::Target {
201        &*self.provider
202    }
203}