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}