mcdata_rs/
cached_data.rs

1use crate::data_source;
2use crate::error::McDataError;
3use crate::features;
4use crate::indexer;
5use crate::loader;
6use crate::structs::*;
7use crate::version::Version;
8use serde_json::Value;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12/// Holds all loaded and indexed Minecraft data for a specific version.
13///
14/// Instances of this struct are cached globally by the `mc_data` function.
15/// Fields are wrapped in `Arc` to allow cheap cloning when retrieving from the cache.
16#[derive(Debug, Clone)]
17pub struct IndexedData {
18    /// The canonical `Version` struct this data corresponds to.
19    pub version: Version,
20
21    // Indexed data structures for quick lookups.
22
23    // Blocks
24    pub blocks_array: Arc<Vec<Block>>,
25    pub blocks_by_id: Arc<HashMap<u32, Block>>,
26    pub blocks_by_name: Arc<HashMap<String, Block>>,
27    pub blocks_by_state_id: Arc<HashMap<u32, Block>>,
28
29    // Items
30    pub items_array: Arc<Vec<Item>>,
31    pub items_by_id: Arc<HashMap<u32, Item>>,
32    pub items_by_name: Arc<HashMap<String, Item>>,
33
34    // Biomes
35    pub biomes_array: Arc<Vec<Biome>>,
36    pub biomes_by_id: Arc<HashMap<u32, Biome>>,
37    pub biomes_by_name: Arc<HashMap<String, Biome>>,
38
39    // Effects (Status Effects)
40    pub effects_array: Arc<Vec<Effect>>,
41    pub effects_by_id: Arc<HashMap<u32, Effect>>,
42    pub effects_by_name: Arc<HashMap<String, Effect>>,
43
44    // Entities
45    pub entities_array: Arc<Vec<Entity>>,
46    pub entities_by_id: Arc<HashMap<u32, Entity>>,
47    pub entities_by_name: Arc<HashMap<String, Entity>>,
48    pub mobs_by_id: Arc<HashMap<u32, Entity>>, // Filtered index for entities of type "mob"
49    pub objects_by_id: Arc<HashMap<u32, Entity>>, // Filtered index for entities of type "object"
50
51    // Sounds
52    pub sounds_array: Arc<Vec<Sound>>,
53    pub sounds_by_id: Arc<HashMap<u32, Sound>>,
54    pub sounds_by_name: Arc<HashMap<String, Sound>>,
55
56    // Particles
57    pub particles_array: Arc<Vec<Particle>>,
58    pub particles_by_id: Arc<HashMap<u32, Particle>>,
59    pub particles_by_name: Arc<HashMap<String, Particle>>,
60
61    // Attributes
62    pub attributes_array: Arc<Vec<Attribute>>,
63    pub attributes_by_name: Arc<HashMap<String, Attribute>>,
64    pub attributes_by_resource: Arc<HashMap<String, Attribute>>, // Index by namespaced key
65
66    // Instruments (Note Block sounds)
67    pub instruments_array: Arc<Vec<Instrument>>,
68    pub instruments_by_id: Arc<HashMap<u32, Instrument>>,
69    pub instruments_by_name: Arc<HashMap<String, Instrument>>,
70
71    // Foods
72    pub foods_array: Arc<Vec<Food>>,
73    pub foods_by_id: Arc<HashMap<u32, Food>>,
74    pub foods_by_name: Arc<HashMap<String, Food>>,
75
76    // Enchantments
77    pub enchantments_array: Arc<Vec<Enchantment>>,
78    pub enchantments_by_id: Arc<HashMap<u32, Enchantment>>,
79    pub enchantments_by_name: Arc<HashMap<String, Enchantment>>,
80
81    // Map Icons
82    pub map_icons_array: Arc<Vec<MapIcon>>,
83    pub map_icons_by_id: Arc<HashMap<u32, MapIcon>>,
84    pub map_icons_by_name: Arc<HashMap<String, MapIcon>>,
85
86    // Windows (Containers/GUIs)
87    pub windows_array: Arc<Vec<Window>>,
88    pub windows_by_id: Arc<HashMap<String, Window>>, // Index by ID (string, potentially namespaced)
89    pub windows_by_name: Arc<HashMap<String, Window>>,
90
91    // Block Loot Tables
92    pub block_loot_array: Arc<Vec<BlockLoot>>,
93    pub block_loot_by_name: Arc<HashMap<String, BlockLoot>>, // Index by block name
94
95    // Entity Loot Tables
96    pub entity_loot_array: Arc<Vec<EntityLoot>>,
97    pub entity_loot_by_name: Arc<HashMap<String, EntityLoot>>, // Index by entity name
98
99    // Indexed Block Collision Shapes
100    pub block_shapes_by_state_id: Arc<HashMap<u32, Vec<[f64; 6]>>>, // Map stateId -> BoundingBoxes
101    pub block_shapes_by_name: Arc<HashMap<String, Vec<[f64; 6]>>>, // Map blockName -> Default State BoundingBoxes
102
103    // Less structured or version-dependent data.
104    /// Raw data from blockCollisionShapes.json, if available for the version.
105    pub block_collision_shapes_raw: Arc<Option<BlockCollisionShapes>>,
106    /// Data from tints.json, if available.
107    pub tints: Arc<Option<Tints>>,
108    /// Data from language.json (typically en_us), if available.
109    pub language: Arc<HashMap<String, String>>,
110    /// Data from legacy.json (mapping old IDs to new), if available.
111    pub legacy: Arc<Option<Legacy>>,
112
113    // Raw JSON values for data types that vary significantly across versions
114    // or are too complex to represent with stable structs easily.
115    pub recipes: Arc<Option<Value>>,
116    pub materials: Arc<Option<Value>>,
117    pub commands: Arc<Option<Value>>,
118    pub protocol: Arc<Option<Value>>, // Raw protocol.json content
119    pub protocol_comments: Arc<Option<Value>>, // Raw protocolComments.json content
120    pub login_packet: Arc<Option<Value>>, // Raw loginPacket.json content
121}
122
123impl IndexedData {
124    /// Loads all required and optional data files for the given canonical version,
125    /// then indexes them into the `IndexedData` struct fields.
126    pub fn load(version: Version) -> Result<Self, McDataError> {
127        log::info!(
128            "Loading and indexing data for version: {} ({:?})",
129            version.minecraft_version,
130            version.edition
131        );
132        let major_version_str = &version.major_version; // Use major version for path lookups
133        let edition = version.edition;
134
135        // Helper macro to load optional data of a specific type.
136        // Handles "file not found" errors gracefully by returning None.
137        // Propagates other errors (e.g., parse errors).
138        macro_rules! load_optional {
139            ($key:expr, $type:ty) => {
140                match loader::load_data::<$type>(edition, major_version_str, $key) {
141                    Ok(data) => {
142                        log::trace!("Successfully loaded optional data for key '{}'", $key);
143                        Some(data)
144                    }
145                    // Treat path/file not found as expected for optional data.
146                    Err(McDataError::DataPathNotFound { .. })
147                    | Err(McDataError::DataFileNotFound { .. }) => {
148                        log::trace!("Optional data key '{}' not found for this version.", $key);
149                        None
150                    }
151                    // Propagate other errors (I/O, JSON parsing, etc.).
152                    Err(e) => {
153                        log::error!("Error loading optional data for key '{}': {}", $key, e);
154                        return Err(e);
155                    }
156                }
157            };
158        }
159        // Helper macro similar to load_optional!, but for loading raw `serde_json::Value`.
160        macro_rules! load_optional_value {
161            ($key:expr) => {
162                match loader::load_data::<Value>(edition, major_version_str, $key) {
163                    Ok(data) => {
164                        log::trace!("Successfully loaded optional value for key '{}'", $key);
165                        Some(data)
166                    }
167                    Err(McDataError::DataPathNotFound { .. })
168                    | Err(McDataError::DataFileNotFound { .. }) => {
169                        log::trace!("Optional value key '{}' not found for this version.", $key);
170                        None
171                    }
172                    Err(e) => {
173                        log::error!("Error loading optional value for key '{}': {}", $key, e);
174                        return Err(e);
175                    }
176                }
177            };
178        }
179
180        // --- Load Raw Data Arrays/Maps ---
181        // Load required data types (expect them to exist for any valid version).
182        let blocks: Vec<Block> = loader::load_data(edition, major_version_str, "blocks")?;
183        let items: Vec<Item> = loader::load_data(edition, major_version_str, "items")?;
184
185        // Load optional data types using the helper macro. Use `unwrap_or_default` for Vec/HashMap.
186        let biomes: Vec<Biome> = load_optional!("biomes", Vec<Biome>).unwrap_or_default();
187        let effects: Vec<Effect> = load_optional!("effects", Vec<Effect>).unwrap_or_default();
188        let entities: Vec<Entity> = load_optional!("entities", Vec<Entity>).unwrap_or_default();
189        let sounds: Vec<Sound> = load_optional!("sounds", Vec<Sound>).unwrap_or_default();
190        let particles: Vec<Particle> =
191            load_optional!("particles", Vec<Particle>).unwrap_or_default();
192        let attributes: Vec<Attribute> =
193            load_optional!("attributes", Vec<Attribute>).unwrap_or_default();
194        let instruments: Vec<Instrument> =
195            load_optional!("instruments", Vec<Instrument>).unwrap_or_default();
196        let foods: Vec<Food> = load_optional!("foods", Vec<Food>).unwrap_or_default();
197        let enchantments: Vec<Enchantment> =
198            load_optional!("enchantments", Vec<Enchantment>).unwrap_or_default();
199        let map_icons: Vec<MapIcon> = load_optional!("mapIcons", Vec<MapIcon>).unwrap_or_default();
200        let windows: Vec<Window> = load_optional!("windows", Vec<Window>).unwrap_or_default();
201        let block_loot: Vec<BlockLoot> =
202            load_optional!("blockLoot", Vec<BlockLoot>).unwrap_or_default();
203        let entity_loot: Vec<EntityLoot> =
204            load_optional!("entityLoot", Vec<EntityLoot>).unwrap_or_default();
205        let block_collision_shapes_raw: Option<BlockCollisionShapes> =
206            load_optional!("blockCollisionShapes", BlockCollisionShapes);
207        let tints: Option<Tints> = load_optional!("tints", Tints);
208        let language: HashMap<String, String> =
209            load_optional!("language", HashMap<String, String>).unwrap_or_default();
210
211        // Load optional raw JSON values.
212        let recipes: Option<Value> = load_optional_value!("recipes");
213        let materials: Option<Value> = load_optional_value!("materials");
214        let commands: Option<Value> = load_optional_value!("commands");
215        let protocol: Option<Value> = load_optional_value!("protocol");
216        let protocol_comments: Option<Value> = load_optional_value!("protocolComments");
217        let login_packet: Option<Value> = load_optional_value!("loginPacket");
218
219        // Load legacy.json (common data, path constructed differently from versioned data).
220        let legacy: Option<Legacy> = {
221            match data_source::get_data_root() {
222                Ok(data_root) => {
223                    let legacy_path =
224                        data_root.join(format!("{}/common/legacy.json", edition.path_prefix()));
225                    match loader::load_data_from_path(&legacy_path) {
226                        Ok(data) => {
227                            log::trace!("Successfully loaded legacy.json for {:?}", edition);
228                            Some(data)
229                        }
230                        // File not found is expected if legacy.json doesn't exist for the edition.
231                        Err(McDataError::IoError { source, .. })
232                            if source.kind() == std::io::ErrorKind::NotFound =>
233                        {
234                            log::trace!("legacy.json not found for {:?}", edition);
235                            None
236                        }
237                        // Log other errors but treat them as non-fatal for legacy data.
238                        Err(e) => {
239                            log::warn!("Failed to load legacy.json for {:?}: {}", edition, e);
240                            None
241                        }
242                    }
243                }
244                Err(e) => {
245                    log::warn!("Could not get data root to load legacy.json: {}", e);
246                    None // Cannot load legacy if data root isn't available.
247                }
248            }
249        };
250
251        // --- Index Loaded Data ---
252        log::debug!("Indexing loaded data...");
253        let (blocks_by_id, blocks_by_name, blocks_by_state_id) = indexer::index_blocks(&blocks);
254        let (items_by_id, items_by_name) = indexer::index_items(&items);
255        let (biomes_by_id, biomes_by_name) = indexer::index_biomes(&biomes);
256        let (effects_by_id, effects_by_name) = indexer::index_effects(&effects);
257        let (entities_by_id, entities_by_name, mobs_by_id, objects_by_id) =
258            indexer::index_entities(&entities);
259        let (sounds_by_id, sounds_by_name) = indexer::index_sounds(&sounds);
260        let (particles_by_id, particles_by_name) = indexer::index_particles(&particles);
261        let (attributes_by_name, attributes_by_resource) = indexer::index_attributes(&attributes);
262        let (instruments_by_id, instruments_by_name) = indexer::index_instruments(&instruments);
263        let (foods_by_id, foods_by_name) = indexer::index_foods(&foods);
264        let (enchantments_by_id, enchantments_by_name) = indexer::index_enchantments(&enchantments);
265        let (map_icons_by_id, map_icons_by_name) = indexer::index_map_icons(&map_icons);
266        let (windows_by_id, windows_by_name) = indexer::index_windows(&windows);
267        let block_loot_by_name = indexer::index_block_loot(&block_loot);
268        let entity_loot_by_name = indexer::index_entity_loot(&entity_loot);
269
270        // Index block collision shapes if the raw data was loaded successfully.
271        let (block_shapes_by_state_id, block_shapes_by_name) = if let Some(ref collision_data) =
272            block_collision_shapes_raw
273        {
274            indexer::index_block_shapes(&blocks_by_state_id, &blocks_by_name, collision_data)
275        } else {
276            // Return empty maps if collision data doesn't exist for this version.
277            log::debug!(
278                "No blockCollisionShapes data found for this version, block shapes will be empty."
279            );
280            (HashMap::new(), HashMap::new())
281        };
282
283        log::info!(
284            "Finished loading and indexing data for {} ({:?})",
285            version.minecraft_version,
286            version.edition
287        );
288
289        // Construct the final IndexedData struct, wrapping fields in Arc.
290        Ok(IndexedData {
291            version,
292            // Arrays
293            blocks_array: Arc::new(blocks),
294            items_array: Arc::new(items),
295            biomes_array: Arc::new(biomes),
296            effects_array: Arc::new(effects),
297            entities_array: Arc::new(entities),
298            sounds_array: Arc::new(sounds),
299            particles_array: Arc::new(particles),
300            attributes_array: Arc::new(attributes),
301            instruments_array: Arc::new(instruments),
302            foods_array: Arc::new(foods),
303            enchantments_array: Arc::new(enchantments),
304            map_icons_array: Arc::new(map_icons),
305            windows_array: Arc::new(windows),
306            block_loot_array: Arc::new(block_loot),
307            entity_loot_array: Arc::new(entity_loot),
308            // Indexed Maps
309            blocks_by_id: Arc::new(blocks_by_id),
310            blocks_by_name: Arc::new(blocks_by_name),
311            blocks_by_state_id: Arc::new(blocks_by_state_id),
312            items_by_id: Arc::new(items_by_id),
313            items_by_name: Arc::new(items_by_name),
314            biomes_by_id: Arc::new(biomes_by_id),
315            biomes_by_name: Arc::new(biomes_by_name),
316            effects_by_id: Arc::new(effects_by_id),
317            effects_by_name: Arc::new(effects_by_name),
318            entities_by_id: Arc::new(entities_by_id),
319            entities_by_name: Arc::new(entities_by_name),
320            mobs_by_id: Arc::new(mobs_by_id),
321            objects_by_id: Arc::new(objects_by_id),
322            sounds_by_id: Arc::new(sounds_by_id),
323            sounds_by_name: Arc::new(sounds_by_name),
324            particles_by_id: Arc::new(particles_by_id),
325            particles_by_name: Arc::new(particles_by_name),
326            attributes_by_name: Arc::new(attributes_by_name),
327            attributes_by_resource: Arc::new(attributes_by_resource),
328            instruments_by_id: Arc::new(instruments_by_id),
329            instruments_by_name: Arc::new(instruments_by_name),
330            foods_by_id: Arc::new(foods_by_id),
331            foods_by_name: Arc::new(foods_by_name),
332            enchantments_by_id: Arc::new(enchantments_by_id),
333            enchantments_by_name: Arc::new(enchantments_by_name),
334            map_icons_by_id: Arc::new(map_icons_by_id),
335            map_icons_by_name: Arc::new(map_icons_by_name),
336            windows_by_id: Arc::new(windows_by_id),
337            windows_by_name: Arc::new(windows_by_name),
338            block_loot_by_name: Arc::new(block_loot_by_name),
339            entity_loot_by_name: Arc::new(entity_loot_by_name),
340            // Indexed Shapes
341            block_shapes_by_state_id: Arc::new(block_shapes_by_state_id),
342            block_shapes_by_name: Arc::new(block_shapes_by_name),
343            // Other Data
344            block_collision_shapes_raw: Arc::new(block_collision_shapes_raw),
345            tints: Arc::new(tints),
346            language: Arc::new(language),
347            legacy: Arc::new(legacy),
348            // Raw Values
349            recipes: Arc::new(recipes),
350            materials: Arc::new(materials),
351            commands: Arc::new(commands),
352            protocol: Arc::new(protocol),
353            protocol_comments: Arc::new(protocol_comments),
354            login_packet: Arc::new(login_packet),
355        })
356    }
357
358    /// Checks if the current data's version is newer than or equal to another version string.
359    ///
360    /// Resolves the `other_version_str` and compares using the `Version` struct's `Ord` implementation.
361    ///
362    /// # Errors
363    /// Returns `McDataError::InvalidVersion` if `other_version_str` is invalid.
364    /// Returns `McDataError::Internal` if attempting to compare versions from different editions.
365    pub fn is_newer_or_equal_to(&self, other_version_str: &str) -> Result<bool, McDataError> {
366        let other_version = crate::version::resolve_version(other_version_str)?;
367        // Ensure comparison happens only within the same edition.
368        if self.version.edition == other_version.edition {
369            Ok(self.version >= other_version) // Uses the Ord implementation for Version
370        } else {
371            Err(McDataError::Internal(format!(
372                "Cannot compare versions from different editions: {:?} ({}) and {:?} ({})",
373                self.version.edition,
374                self.version.minecraft_version,
375                other_version.edition,
376                other_version.minecraft_version
377            )))
378        }
379    }
380
381    /// Checks if the current data's version is strictly older than another version string.
382    ///
383    /// Resolves the `other_version_str` and compares using the `Version` struct's `Ord` implementation.
384    ///
385    /// # Errors
386    /// Returns `McDataError::InvalidVersion` if `other_version_str` is invalid.
387    /// Returns `McDataError::Internal` if attempting to compare versions from different editions.
388    pub fn is_older_than(&self, other_version_str: &str) -> Result<bool, McDataError> {
389        let other_version = crate::version::resolve_version(other_version_str)?;
390        // Ensure comparison happens only within the same edition.
391        if self.version.edition == other_version.edition {
392            Ok(self.version < other_version) // Uses the Ord implementation for Version
393        } else {
394            Err(McDataError::Internal(format!(
395                "Cannot compare versions from different editions: {:?} ({}) and {:?} ({})",
396                self.version.edition,
397                self.version.minecraft_version,
398                other_version.edition,
399                other_version.minecraft_version
400            )))
401        }
402    }
403
404    /// Checks support for a named feature based on the current data's version.
405    ///
406    /// Consults the `features.json` data and returns the feature's value (often boolean,
407    /// but can be other JSON types) if supported for this version, or `Value::Bool(false)` otherwise.
408    ///
409    /// # Errors
410    /// Returns `McDataError` if feature data or version information cannot be loaded or resolved.
411    pub fn support_feature(&self, feature_name: &str) -> Result<Value, McDataError> {
412        features::get_feature_support(&self.version, feature_name)
413    }
414}