diff --git a/Cargo.lock b/Cargo.lock index 71b562b66b7f692c404827ce0d0122f2da8390a7..8c4ca699098826f1ac6f1285850b8dbb689e4782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1567,7 +1567,7 @@ dependencies = [ [[package]] name = "micro_ldtk" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", "bevy", diff --git a/src/assets/asset_events.rs b/src/assets/asset_events.rs index 03cde4d1f565c6259da52e90960235af515e8671..c7acc8ae2a88c5a5baa2a2b54c17083e6165a3dc 100644 --- a/src/assets/asset_events.rs +++ b/src/assets/asset_events.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use bevy::prelude::*; use crate::assets::{LevelIndex, TileMetadata, TilesetIndex}; -use crate::ldtk::Project; +use crate::ldtk::{Level, Project}; use crate::{LdtkLevel, LevelDataUpdated}; pub fn handle_ldtk_project_events( @@ -18,9 +18,11 @@ pub fn handle_ldtk_project_events( AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { if let Some(project) = assets.get(handle) { for level in project.get_all_levels() { - level_index - .insert(level.identifier.clone(), LdtkLevel::from(level.clone())); - update_events.send(LevelDataUpdated(level.identifier.clone())); + if level.external_rel_path.is_none() { + level_index + .insert(level.identifier.clone(), LdtkLevel::from(level.clone())); + update_events.send(LevelDataUpdated(level.identifier.clone())); + } } for tileset in &project.defs.tilesets { @@ -39,3 +41,22 @@ pub fn handle_ldtk_project_events( } } } + +pub fn handle_ldtk_level_events( + mut events: EventReader<AssetEvent<Level>>, + assets: Res<Assets<Level>>, + mut level_index: ResMut<LevelIndex>, + mut update_events: EventWriter<LevelDataUpdated>, +) { + for event in events.iter() { + match event { + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { + if let Some(level) = assets.get(handle) { + level_index.insert(level.identifier.clone(), LdtkLevel::from(level.clone())); + update_events.send(LevelDataUpdated(level.identifier.clone())); + } + } + _ => {} + } + } +} diff --git a/src/ldtk/mod.rs b/src/ldtk/mod.rs index 51cc19d870b13ba80a3bd011b64b329777bb23c4..8d0cbafa8ae9dccdf82c7eade64f1181b9694528 100644 --- a/src/ldtk/mod.rs +++ b/src/ldtk/mod.rs @@ -15,7 +15,7 @@ mod data_1_2_5; #[cfg(feature = "ldtk_1_3_0")] mod data_1_3_0; -use bevy::asset::{AssetLoader, BoxedFuture, LoadContext, LoadedAsset}; +use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset}; use bevy::reflect::{TypePath, TypeUuid, Uuid}; #[cfg(feature = "ldtk_1_0_0")] pub use data_1_0_0::*; @@ -33,6 +33,7 @@ pub use data_1_2_4::*; pub use data_1_2_5::*; #[cfg(feature = "ldtk_1_3_0")] pub use data_1_3_0::*; +use serde::Deserialize; #[derive(thiserror::Error, Debug)] pub enum ParseError { @@ -40,6 +41,42 @@ pub enum ParseError { 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); } @@ -53,11 +90,21 @@ impl TypePath for Project { } } -impl Project { - pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> { - serde_json::from_slice(bytes).map_err(|e| ParseError::SerdeError(format!("{}", e))) +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 Project { pub fn get_all_levels(&self) -> Vec<&Level> { if !self.worlds.is_empty() { self.worlds @@ -99,26 +146,6 @@ impl Project { pub type LdtkProject = Project; -impl<'a> From<&'a [u8]> for Project { - fn from(value: &'a [u8]) -> Self { - #[cfg(feature = "no_panic")] - { - match Project::from_bytes(value) { - Ok(val) => val, - Err(e) => { - log::error!("{}", e); - std::process::abort(); - } - } - } - - #[cfg(not(feature = "no_panic"))] - { - Project::from_bytes(value).expect("Failed to parse ldtk project file") - } - } -} - #[derive(Default)] pub struct LdtkLoader; impl AssetLoader for LdtkLoader { @@ -128,7 +155,27 @@ impl AssetLoader for LdtkLoader { load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, anyhow::Result<(), anyhow::Error>> { Box::pin(async move { - load_context.set_default_asset(LoadedAsset::new(Project::from_bytes(bytes)?)); + let project = Project::from_bytes(bytes)?; + + let sub_levels = project + .levels + .iter() + .flat_map(|level| { + level + .external_rel_path + .as_ref() + .map(|rel_path| (level.identifier.clone(), rel_path.clone())) + }) + .collect::<Vec<(String, String)>>(); + + let asset = LoadedAsset::new(project).with_dependencies( + sub_levels + .into_iter() + .map(|(id, path)| AssetPath::new(path.into(), Some(id))) + .collect(), + ); + + load_context.set_default_asset(asset); Ok(()) }) } @@ -137,6 +184,24 @@ impl AssetLoader for LdtkLoader { &["ldtk"] } } +#[derive(Default)] +pub struct LdtkLevelLoader; +impl AssetLoader for LdtkLevelLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, anyhow::Result<(), anyhow::Error>> { + Box::pin(async move { + load_context.set_default_asset(LoadedAsset::new(Level::from_bytes(bytes)?)); + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { + &["ldtkl"] + } +} #[cfg(feature = "autotile")] mod autotile_support { @@ -215,7 +280,7 @@ mod autotile_support { #[cfg(test)] mod test { - use crate::ldtk::Project; + use crate::ldtk::{LdtkFromBytes, Project}; #[cfg_attr(feature = "ldtk_1_2_5", test)] pub fn load_project() { diff --git a/src/lib.rs b/src/lib.rs index 6acb9e7173f8dc667a89e838a82e9bd3b59d7cc3..fb70b85650b46937e8237c264c147516a10240ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,10 +75,13 @@ mod __plugin { { app.add_event::<super::system::LevelDataUpdated>() .add_asset::<super::ldtk::Project>() + .add_asset::<super::ldtk::Level>() .add_asset_loader(super::ldtk::LdtkLoader) + .add_asset_loader(super::ldtk::LdtkLevelLoader) .init_resource::<super::assets::TilesetIndex>() .init_resource::<super::assets::LevelIndex>() - .add_systems(Update, super::assets::handle_ldtk_project_events); + .add_systems(Update, super::assets::handle_ldtk_project_events) + .add_systems(Update, super::assets::handle_ldtk_level_events); } } }