Skip to main content

micro_ldtk/ldtk/
mod.rs

1#[cfg(feature = "ldtk_1_0_0")]
2mod data_1_0_0;
3#[cfg(any(feature = "ldtk_1_1_1", feature = "ldtk_1_1_0"))]
4mod data_1_1_0;
5#[cfg(any(feature = "ldtk_1_1_3", feature = "ldtk_1_1_2"))]
6mod data_1_1_2;
7#[cfg(any(feature = "ldtk_1_2_1", feature = "ldtk_1_2_0"))]
8mod data_1_2_1;
9#[cfg(any(feature = "ldtk_1_2_3", feature = "ldtk_1_2_2"))]
10mod data_1_2_2;
11#[cfg(feature = "ldtk_1_2_4")]
12mod data_1_2_4;
13#[cfg(feature = "ldtk_1_2_5")]
14mod data_1_2_5;
15#[cfg(feature = "ldtk_1_3_0")]
16mod data_1_3_0;
17#[cfg(any(feature = "ldtk_1_4_1", feature = "ldtk_1_4_0"))]
18mod data_1_4_0;
19#[cfg(feature = "ldtk_1_5_3")]
20mod data_1_5_3;
21
22use crate::ldtk;
23#[cfg(feature = "ldtk_1_0_0")]
24pub use data_1_0_0::*;
25#[cfg(any(feature = "ldtk_1_1_1", feature = "ldtk_1_1_0"))]
26pub use data_1_1_0::*;
27#[cfg(any(feature = "ldtk_1_1_3", feature = "ldtk_1_1_2"))]
28pub use data_1_1_2::*;
29#[cfg(any(feature = "ldtk_1_2_1", feature = "ldtk_1_2_0"))]
30pub use data_1_2_1::*;
31#[cfg(any(feature = "ldtk_1_2_3", feature = "ldtk_1_2_2"))]
32pub use data_1_2_2::*;
33#[cfg(feature = "ldtk_1_2_4")]
34pub use data_1_2_4::*;
35#[cfg(feature = "ldtk_1_2_5")]
36pub use data_1_2_5::*;
37#[cfg(feature = "ldtk_1_3_0")]
38pub use data_1_3_0::*;
39#[cfg(any(feature = "ldtk_1_4_1", feature = "ldtk_1_4_0"))]
40pub use data_1_4_0::*;
41#[cfg(feature = "ldtk_1_5_3")]
42pub use data_1_5_3::*;
43use serde::Deserialize;
44
45#[derive(thiserror::Error, Debug)]
46pub enum ParseError {
47	#[error("Failed to parse file: {0}")]
48	SerdeError(String),
49}
50
51pub trait LdtkFromBytes<'a>: Deserialize<'a> {
52	fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
53		serde_json::from_slice(bytes).map_err(|e| ParseError::SerdeError(format!("{}", e)))
54	}
55}
56
57macro_rules! impl_from_bytes {
58	($type: tt) => {
59		impl<'a> From<&'a [u8]> for $type {
60			fn from(value: &'a [u8]) -> Self {
61				#[cfg(feature = "no_panic")]
62				{
63					match $type::from_bytes(value) {
64						Ok(val) => val,
65						Err(e) => {
66							log::error!("{}", e);
67							std::process::abort();
68						}
69					}
70				}
71
72				#[cfg(not(feature = "no_panic"))]
73				{
74					$type::from_bytes(value).expect("Failed to parse ldtk file")
75				}
76			}
77		}
78	};
79}
80
81impl LdtkFromBytes<'_> for Level {}
82impl LdtkFromBytes<'_> for Project {}
83
84impl_from_bytes!(Level);
85impl_from_bytes!(Project);
86
87#[cfg(feature = "bevy")]
88mod _bevy_impl {
89	use super::*;
90	use bevy_asset::io::Reader;
91	use bevy_asset::{Asset, Handle};
92	use bevy_asset::{AssetLoader, LoadContext, UntypedAssetId, VisitAssetDependencies};
93	use bevy_reflect::TypePath;
94
95	impl TypePath for Project {
96		fn type_path() -> &'static str {
97			"micro_ldtk::ldtk::Project"
98		}
99		fn short_type_path() -> &'static str {
100			"Project"
101		}
102	}
103
104	impl VisitAssetDependencies for Project {
105		fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
106	}
107
108	impl Asset for Project {}
109
110	impl TypePath for Level {
111		fn type_path() -> &'static str {
112			"micro_ldtk::ldtk::Level"
113		}
114		fn short_type_path() -> &'static str {
115			"Level"
116		}
117	}
118
119	impl VisitAssetDependencies for Level {
120		fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
121	}
122
123	impl Asset for Level {}
124
125	#[derive(Asset, TypePath)]
126	pub struct LevelSet(pub Vec<Handle<Level>>);
127
128	#[derive(Default, TypePath)]
129	pub struct LdtkLoader;
130	impl AssetLoader for LdtkLoader {
131		type Asset = Project;
132		type Settings = ();
133		type Error = LdtkLoadError;
134
135		async fn load(
136			&self,
137			reader: &mut dyn Reader,
138			_settings: &Self::Settings,
139			load_context: &mut LoadContext<'_>,
140		) -> Result<Self::Asset, Self::Error> {
141			let mut bytes = Vec::new();
142			reader.read_to_end(&mut bytes).await?;
143			let project = Project::from_bytes(bytes.as_slice())?;
144
145			let levels = project
146				.levels
147				.iter()
148				.flat_map(|level| {
149					level
150						.external_rel_path
151						.as_ref()
152						.map(|path| (level.identifier.clone(), path))
153				})
154				.collect::<Vec<(String, &String)>>();
155
156			let parent_path = load_context.path().parent().map(|pp| pp.path().to_path_buf());
157			let mut level_set = Vec::with_capacity(levels.len());
158
159			for (_, path) in levels {
160				level_set.push(match &parent_path {
161					Some(parent) => load_context.load::<Level>(parent.join(path)),
162					None => load_context.load::<Level>(path),
163				});
164			}
165
166			load_context.add_labeled_asset("ExternalLevels".into(), LevelSet(level_set));
167
168			Ok(project)
169		}
170
171		fn extensions(&self) -> &[&str] {
172			&["ldtk"]
173		}
174	}
175
176	#[derive(Default, TypePath)]
177	pub struct LdtkLevelLoader;
178	impl AssetLoader for LdtkLevelLoader {
179		type Asset = Level;
180		type Settings = ();
181		type Error = LdtkLoadError;
182
183		async fn load(
184			&self,
185			reader: &mut dyn Reader,
186			_settings: &Self::Settings,
187			_load_context: &mut LoadContext<'_>,
188		) -> Result<Self::Asset, Self::Error> {
189			let mut bytes = Vec::new();
190			reader.read_to_end(&mut bytes).await?;
191			let level = Level::from_bytes(bytes.as_slice())?;
192			Ok(level)
193		}
194		fn extensions(&self) -> &[&str] {
195			&["ldtkl"]
196		}
197	}
198}
199
200#[cfg(feature = "bevy")]
201pub use _bevy_impl::{LdtkLevelLoader, LdtkLoader, LevelSet};
202
203impl Project {
204	pub fn get_all_levels(&self) -> Vec<&Level> {
205		if !self.worlds.is_empty() {
206			self.worlds
207				.iter()
208				.flat_map(|world| world.levels.iter())
209				.collect()
210		} else {
211			self.levels.iter().collect()
212		}
213	}
214
215	#[cfg(not(feature = "_supports_worlds"))]
216	pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
217		vec![]
218	}
219
220	#[cfg(feature = "_supports_worlds")]
221	pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
222		let id = identifier.to_string();
223		self.worlds
224			.iter()
225			.find(|world| world.identifier == id)
226			.map(|list| list.levels.iter().collect())
227			.unwrap_or_else(Vec::new)
228	}
229}
230
231#[derive(Debug, thiserror::Error)]
232pub enum LdtkLoadError {
233	#[error(transparent)]
234	Io(#[from] std::io::Error),
235	#[error(transparent)]
236	Serde(#[from] serde_json::Error),
237	#[error(transparent)]
238	Ldtk(#[from] ldtk::ParseError),
239}
240
241pub type LdtkProject = Project;
242
243#[cfg(feature = "autotile")]
244mod autotile_support {
245	use crate::ldtk::{AutoLayerRuleDefinition, AutoLayerRuleGroup, Project};
246	use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus};
247
248	#[cfg(feature = "_optional_tile_list")]
249	fn create_output(rule: &AutoLayerRuleDefinition) -> TileOutput {
250		TileOutput::Random(
251			rule.tile_rects_ids
252				.iter()
253				.flatten()
254				.map(|val| *val as i32)
255				.collect(),
256		)
257	}
258
259	#[cfg(not(feature = "_optional_tile_list"))]
260	fn create_output(rule: &AutoLayerRuleDefinition) -> TileOutput {
261		TileOutput::Random(rule.tile_ids.iter().map(|val| (*val).as_()).collect())
262	}
263
264	impl From<&AutoLayerRuleGroup> for AutoRuleSet {
265		fn from(value: &AutoLayerRuleGroup) -> Self {
266			let set = value
267				.rules
268				.iter()
269				.filter_map(|rule| match rule.size {
270					1 => Some(AutoTileRule {
271						chance: rule.chance as f32,
272						output: create_output(rule),
273						matcher: TileMatcher::single(TileStatus::from(rule.pattern[0])),
274					}),
275					_ => TileMatcher::try_from(rule.pattern.as_slice())
276						.ok()
277						.map(|matcher| AutoTileRule {
278							matcher,
279							chance: rule.chance as f32,
280							output: create_output(rule),
281						}),
282				})
283				.collect();
284
285			AutoRuleSet(set)
286		}
287	}
288
289	impl From<&Project> for AutoRuleSet {
290		fn from(value: &Project) -> Self {
291			let mut base_set = AutoRuleSet::default();
292
293			#[cfg(feature = "_supports_ui_tags")]
294			{
295				for layers in value.defs.layers.iter() {
296					for rule_group in layers.auto_rule_groups.iter() {
297						base_set = base_set + rule_group.into();
298					}
299				}
300			}
301
302			base_set
303		}
304	}
305}
306
307#[cfg(test)]
308mod test {
309	#![allow(dead_code)]
310	use crate::ldtk::{LdtkFromBytes, Project};
311
312	#[cfg_attr(feature = "ldtk_1_2_5", test)]
313	pub fn load_project() {
314		const PROJECT_DATA: &[u8] = include_bytes!("./test_data/ver_1_2_5.ldtk");
315
316		let project = Project::from_bytes(PROJECT_DATA).expect("Failed to parse project file");
317		for layer in project.defs.layers.iter() {
318			for _auto_rule_group in layer.auto_rule_groups.iter() {}
319		}
320	}
321}