#[cfg(feature = "ldtk_1_0_0")] mod data_1_0_0; #[cfg(any(feature = "ldtk_1_1_1", feature = "ldtk_1_1_0"))] mod data_1_1_0; #[cfg(any(feature = "ldtk_1_1_3", feature = "ldtk_1_1_2"))] mod data_1_1_2; #[cfg(any(feature = "ldtk_1_2_1", feature = "ldtk_1_2_0"))] mod data_1_2_1; #[cfg(any(feature = "ldtk_1_2_3", feature = "ldtk_1_2_2"))] mod data_1_2_2; #[cfg(feature = "ldtk_1_2_4")] mod data_1_2_4; #[cfg(feature = "ldtk_1_2_5")] mod data_1_2_5; #[cfg(feature = "ldtk_1_3_0")] mod data_1_3_0; #[cfg(any(feature = "ldtk_1_4_1", feature = "ldtk_1_4_0"))] mod data_1_4_0; #[cfg(any(feature = "ldtk_1_5_3"))] mod data_1_5_3; use crate::ldtk; #[cfg(feature = "ldtk_1_0_0")] pub use data_1_0_0::*; #[cfg(any(feature = "ldtk_1_1_1", feature = "ldtk_1_1_0"))] pub use data_1_1_0::*; #[cfg(any(feature = "ldtk_1_1_3", feature = "ldtk_1_1_2"))] pub use data_1_1_2::*; #[cfg(any(feature = "ldtk_1_2_1", feature = "ldtk_1_2_0"))] pub use data_1_2_1::*; #[cfg(any(feature = "ldtk_1_2_3", feature = "ldtk_1_2_2"))] pub use data_1_2_2::*; #[cfg(feature = "ldtk_1_2_4")] pub use data_1_2_4::*; #[cfg(feature = "ldtk_1_2_5")] pub use data_1_2_5::*; #[cfg(feature = "ldtk_1_3_0")] pub use data_1_3_0::*; #[cfg(any(feature = "ldtk_1_4_1", feature = "ldtk_1_4_0"))] pub use data_1_4_0::*; #[cfg(any(feature = "ldtk_1_5_3"))] pub use data_1_5_3::*; use serde::Deserialize; #[derive(thiserror::Error, Debug)] pub enum ParseError { #[error("Failed to parse file: {0}")] SerdeError(String), } pub trait LdtkFromBytes<'a>: Deserialize<'a> { fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> { serde_json::from_slice(bytes).map_err(|e| ParseError::SerdeError(format!("{}", e))) } } macro_rules! impl_from_bytes { ($type: tt) => { impl<'a> From<&'a [u8]> for $type { fn from(value: &'a [u8]) -> Self { #[cfg(feature = "no_panic")] { match $type::from_bytes(value) { Ok(val) => val, Err(e) => { log::error!("{}", e); std::process::abort(); } } } #[cfg(not(feature = "no_panic"))] { $type::from_bytes(value).expect("Failed to parse ldtk file") } } } }; } impl<'a> LdtkFromBytes<'a> for Level {} impl<'a> LdtkFromBytes<'a> for Project {} impl_from_bytes!(Level); impl_from_bytes!(Project); #[cfg(feature = "bevy")] mod _bevy_impl { use super::*; use bevy::asset::io::Reader; use bevy::asset::{AssetLoader, AsyncReadExt, LoadContext, UntypedAssetId, VisitAssetDependencies}; use bevy::prelude::{Asset, Handle}; use bevy::reflect::TypePath; impl TypePath for Project { fn type_path() -> &'static str { "micro_ldtk::ldtk::Project" } fn short_type_path() -> &'static str { "Project" } } impl VisitAssetDependencies for Project { fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {} } impl Asset for Project {} impl TypePath for Level { fn type_path() -> &'static str { "micro_ldtk::ldtk::Level" } fn short_type_path() -> &'static str { "Level" } } impl VisitAssetDependencies for Level { fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {} } impl Asset for Level {} #[derive(Asset, TypePath)] pub struct LevelSet(pub Vec<Handle<Level>>); #[derive(Default)] pub struct LdtkLoader; impl AssetLoader for LdtkLoader { type Asset = Project; type Settings = (); type Error = LdtkLoadError; async fn load( &self, reader: &mut dyn Reader, _settings: &Self::Settings, load_context: &mut LoadContext<'_>, ) -> Result<Self::Asset, Self::Error> { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let project = Project::from_bytes(bytes.as_slice())?; let levels = project .levels .iter() .flat_map(|level| { log::debug!( "Checking if level is external: {} [{}]", level.identifier, level.external_rel_path.is_some() ); level .external_rel_path .as_ref() .map(|path| (level.identifier.clone(), path)) }) .collect::<Vec<(String, &String)>>(); let parent_path = load_context.path().parent().map(|pp| pp.to_path_buf()); let mut level_set = Vec::with_capacity(levels.len()); for (_, path) in levels { level_set.push(match &parent_path { Some(parent) => load_context.load::<Level>(parent.join(path)), None => load_context.load::<Level>(path), }); } load_context.add_labeled_asset( format!("{}ExternalLevels", project.iid), LevelSet(level_set), ); Ok(project) } fn extensions(&self) -> &[&str] { &["ldtk"] } } #[derive(Default)] pub struct LdtkLevelLoader; impl AssetLoader for LdtkLevelLoader { type Asset = Level; type Settings = (); type Error = LdtkLoadError; async fn load( &self, reader: &mut dyn Reader, _settings: &Self::Settings, _load_context: &mut LoadContext<'_>, ) -> Result<Self::Asset, Self::Error> { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let level = Level::from_bytes(bytes.as_slice())?; Ok(level) } fn extensions(&self) -> &[&str] { &["ldtkl"] } } } #[cfg(feature = "bevy")] pub use _bevy_impl::{LdtkLoader, LdtkLevelLoader, LevelSet}; impl Project { pub fn get_all_levels(&self) -> Vec<&Level> { if !self.worlds.is_empty() { self.worlds .iter() .flat_map(|world| world.levels.iter()) .collect() } else { self.levels.iter().collect() } } #[cfg(any( feature = "ldtk_1_2_5", feature = "ldtk_1_2_4", feature = "ldtk_1_2_3", feature = "ldtk_1_2_2", feature = "ldtk_1_2_1", feature = "ldtk_1_2_0", feature = "ldtk_1_1_3", feature = "ldtk_1_1_2", feature = "ldtk_1_1_1", feature = "ldtk_1_1_0", feature = "ldtk_1_0_0" ))] pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> { vec![] } #[cfg(any(feature = "ldtk_1_3_0", feature = "ldtk_1_4_0", feature = "ldtk_1_4_1"))] pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> { let id = identifier.to_string(); self.worlds .iter() .find(|world| world.identifier == id) .map(|list| list.levels.iter().collect()) .unwrap_or_else(Vec::new) } } #[derive(Debug, thiserror::Error)] pub enum LdtkLoadError { #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] Serde(#[from] serde_json::Error), #[error(transparent)] Ldtk(#[from] ldtk::ParseError), } pub type LdtkProject = Project; #[cfg(feature = "autotile")] mod autotile_support { use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus}; use crate::ldtk::{AutoLayerRuleDefinition, AutoLayerRuleGroup, Project}; #[cfg(feature = "_optional_tile_list")] fn create_output(rule: &AutoLayerRuleDefinition) -> TileOutput { TileOutput::Random( rule.tile_rects_ids .iter() .flatten() .map(|val| *val as i32) .collect(), ) } #[cfg(not(feature = "_optional_tile_list"))] fn create_output(rule: &AutoLayerRuleDefinition) -> TileOutput { TileOutput::Random(rule.tile_ids.iter().map(|val| *val as usize).collect()) } impl From<&AutoLayerRuleGroup> for AutoRuleSet { fn from(value: &AutoLayerRuleGroup) -> Self { let set = value .rules .iter() .filter_map(|rule| match rule.size { 1 => Some(AutoTileRule { chance: rule.chance as f32, output: create_output(rule), matcher: TileMatcher::single(TileStatus::from( rule.pattern[0], )), }), _ => { TileMatcher::try_from(rule.pattern.as_slice()) .ok().map(|matcher| { AutoTileRule { matcher, chance: rule.chance as f32, output: create_output(rule), } }) } }) .collect(); AutoRuleSet(set) } } impl From<&Project> for AutoRuleSet { fn from(value: &Project) -> Self { let mut base_set = AutoRuleSet::default(); for layers in value.defs.layers.iter() { for rule_group in layers.auto_rule_groups.iter() { base_set = base_set + rule_group.into(); } } base_set } } } #[cfg(test)] mod test { #![allow(dead_code)] use crate::ldtk::{LdtkFromBytes, Project}; #[cfg_attr(feature = "ldtk_1_2_5", test)] pub fn load_project() { const PROJECT_DATA: &[u8] = include_bytes!("./test_data/ver_1_2_5.ldtk"); let project = Project::from_bytes(PROJECT_DATA).expect("Failed to parse project file"); for layer in project.defs.layers.iter() { for _auto_rule_group in layer.auto_rule_groups.iter() {} } } }