#[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; use bevy::asset::io::Reader; use bevy::asset::{ AssetLoader, AsyncReadExt, BoxedFuture, LoadContext, UntypedAssetId, VisitAssetDependencies, }; use bevy::prelude::Asset; use bevy::reflect::{TypePath, TypeUuid, Uuid}; 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::*; 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); impl TypeUuid for Project { const TYPE_UUID: Uuid = Uuid::from_u128(87988914102923589138720617793417023455); } 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 TypeUuid for Level { const TYPE_UUID: Uuid = Uuid::from_u128(18486863672600588966868281477384349187); } impl TypePath for Level { fn type_path() -> &'static str { "micro_ld0tk::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 {} 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; #[derive(Default)] pub struct LdtkLoader; impl AssetLoader for LdtkLoader { type Asset = Project; type Settings = (); type Error = LdtkLoadError; fn load<'a>( &'a self, reader: &'a mut Reader, _settings: &'a Self::Settings, load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { Box::pin(async move { 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)) }); let parent_path = load_context.path().parent().map(|pp| pp.to_path_buf()); for (id, path) in levels { load_context.labeled_asset_scope(id, |lc| { match &parent_path { Some(parent) => lc.load::<Level>(parent.join(path)), None => lc.load::<Level>(path), }; }); } Ok(project) }) } fn extensions(&self) -> &[&str] { &["ldtk"] } } #[derive(Default)] pub struct LdtkLevelLoader; impl AssetLoader for LdtkLevelLoader { type Asset = Level; type Settings = (); type Error = LdtkLoadError; fn load<'a>( &'a self, reader: &'a mut Reader, _settings: &'a Self::Settings, _load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { Box::pin(async move { 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 = "autotile")] mod autotile_support { use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus}; use crate::ldtk::{AutoLayerRuleGroup, Project}; impl From<&AutoLayerRuleGroup> for AutoRuleSet { fn from(value: &AutoLayerRuleGroup) -> Self { let set = value .rules .iter() .filter_map(|rule| match rule.size { 1 => { let rule = AutoTileRule { chance: rule.chance as f32, output: TileOutput::Random( rule.tile_ids.iter().map(|val| *val as usize).collect(), ), matcher: TileMatcher::single_match(TileStatus::from_ldtk_value( rule.pattern[0], )), }; Some(rule) } 3 => { if rule.pattern.len() == 9 { let matcher = TileMatcher([ TileStatus::from_ldtk_value(rule.pattern[0]), TileStatus::from_ldtk_value(rule.pattern[1]), TileStatus::from_ldtk_value(rule.pattern[2]), TileStatus::from_ldtk_value(rule.pattern[3]), TileStatus::from_ldtk_value(rule.pattern[4]), TileStatus::from_ldtk_value(rule.pattern[5]), TileStatus::from_ldtk_value(rule.pattern[6]), TileStatus::from_ldtk_value(rule.pattern[7]), TileStatus::from_ldtk_value(rule.pattern[8]), ]); let rule = AutoTileRule { chance: rule.chance as f32, matcher, output: TileOutput::Random( rule.tile_ids.iter().map(|val| *val as usize).collect(), ), }; Some(rule) } else { None } } _ => None, }) .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() {} } } }