#[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(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(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 LdtkFromBytes<'_> for Level {}
impl LdtkFromBytes<'_> 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::{Asset, Handle};
use bevy_asset::{AssetLoader, LoadContext, UntypedAssetId, VisitAssetDependencies};
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, TypePath)]
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| {
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.path().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("ExternalLevels".into(), LevelSet(level_set));
Ok(project)
}
fn extensions(&self) -> &[&str] {
&["ldtk"]
}
}
#[derive(Default, TypePath)]
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::{LdtkLevelLoader, LdtkLoader, 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(not(feature = "_supports_worlds"))]
pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
vec![]
}
#[cfg(feature = "_supports_worlds")]
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 crate::ldtk::{AutoLayerRuleDefinition, AutoLayerRuleGroup, Project};
use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus};
#[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_()).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();
#[cfg(feature = "_supports_ui_tags")]
{
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() {}
}
}
}