Skip to content
Snippets Groups Projects
mod.rs 8.14 KiB
Newer Older
#[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;
Louis's avatar
Louis committed
#[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, AssetPath, AsyncReadExt, BoxedFuture, LoadContext, LoadedAsset, UntypedAssetId,
	VisitAssetDependencies,
};
use bevy::prelude::Asset;
use bevy::reflect::{TypePath, TypeUuid, Uuid};
use crate::{ldtk, LdtkLevel};
#[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::*;
Louis's avatar
Louis committed
#[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::*;
Louis's avatar
Louis committed
use serde::Deserialize;
#[derive(thiserror::Error, Debug)]
pub enum ParseError {
	#[error("Failed to parse file: {0}")]
	SerdeError(String),
Louis's avatar
Louis committed
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 {}

Louis's avatar
Louis committed
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"
Louis's avatar
Louis committed

Louis's avatar
Louis committed
	fn short_type_path() -> &'static str {
		"Level"
	}
}

impl VisitAssetDependencies for Level {
	fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
}

impl Asset for Level {}

Louis's avatar
Louis committed
impl Project {
Louis's avatar
Louis committed
	pub fn get_all_levels(&self) -> Vec<&Level> {
		if !self.worlds.is_empty() {
Louis's avatar
Louis committed
			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"))]
Louis's avatar
Louis committed
	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;

		reader: &'a mut Reader,
		_settings: &'a Self::Settings,
		load_context: &'a mut LoadContext,
	) -> BoxedFuture<'a, 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 mut 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))
			});

			for (id, path) in levels {
				load_context.labeled_asset_scope(id, |lc| {
					lc.load::<Level>(path);
				});
			}

			Ok(project)
Louis's avatar
Louis committed
#[derive(Default)]
pub struct LdtkLevelLoader;
impl AssetLoader for LdtkLevelLoader {
	type Asset = Level;
	type Settings = ();
	type Error = LdtkLoadError;

Louis's avatar
Louis committed
	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>> {
Louis's avatar
Louis committed
		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)
Louis's avatar
Louis committed
		})
	}
	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(
Louis's avatar
Louis committed
								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(
Louis's avatar
Louis committed
									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 {
Louis's avatar
Louis committed
	use crate::ldtk::{LdtkFromBytes, Project};
Louis's avatar
Louis committed
	#[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() {
Louis's avatar
Louis committed
			for _auto_rule_group in layer.auto_rule_groups.iter() {}