Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • microhacks/bevy-micro-ldtk
1 result
Show changes
Commits on Source (2)
...@@ -132,12 +132,6 @@ dependencies = [ ...@@ -132,12 +132,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]] [[package]]
name = "approx" name = "approx"
version = "0.5.1" version = "0.5.1"
...@@ -2235,9 +2229,8 @@ dependencies = [ ...@@ -2235,9 +2229,8 @@ dependencies = [
[[package]] [[package]]
name = "micro_ldtk" name = "micro_ldtk"
version = "0.14.0" version = "0.14.1"
dependencies = [ dependencies = [
"anyhow",
"bevy", "bevy",
"glam", "glam",
"log", "log",
...@@ -2246,6 +2239,7 @@ dependencies = [ ...@@ -2246,6 +2239,7 @@ dependencies = [
"quadtree_rs", "quadtree_rs",
"serde", "serde",
"serde_json", "serde_json",
"test-case",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
...@@ -3379,6 +3373,39 @@ dependencies = [ ...@@ -3379,6 +3373,39 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "test-case"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-core"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
dependencies = [
"cfg-if",
"proc-macro2 1.0.94",
"quote 1.0.40",
"syn 2.0.100",
]
[[package]]
name = "test-case-macros"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2 1.0.94",
"quote 1.0.40",
"syn 2.0.100",
"test-case-core",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
......
[package] [package]
name = "micro_ldtk" name = "micro_ldtk"
version = "0.14.0" version = "0.14.1"
edition = "2021" edition = "2021"
authors = [ authors = [
...@@ -12,10 +12,10 @@ license = "Apache-2.0" ...@@ -12,10 +12,10 @@ license = "Apache-2.0"
[features] [features]
default = ["ldtk_1_5_3", "autotile"] default = ["ldtk_1_5_3", "autotile"]
ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list"] ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list", "_supports_ui_tags", "_supports_worlds"]
ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup"] ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup"] ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
ldtk_1_3_0 = ["_supports_ldtk"] ldtk_1_3_0 = ["_supports_ldtk", "_supports_worlds"]
ldtk_1_2_5 = ["_supports_ldtk"] ldtk_1_2_5 = ["_supports_ldtk"]
ldtk_1_2_4 = ["_supports_ldtk"] ldtk_1_2_4 = ["_supports_ldtk"]
ldtk_1_2_3 = ["_supports_ldtk"] ldtk_1_2_3 = ["_supports_ldtk"]
...@@ -29,17 +29,18 @@ ldtk_1_1_0 = ["_supports_ldtk"] ...@@ -29,17 +29,18 @@ ldtk_1_1_0 = ["_supports_ldtk"]
ldtk_1_0_0 = ["_supports_ldtk"] ldtk_1_0_0 = ["_supports_ldtk"]
autotile = ["dep:micro_autotile"] autotile = ["dep:micro_autotile"]
bevy = ["dep:bevy"] bevy = ["dep:bevy"]
_supports_ui_tags = []
_supports_intgridgroup = [] _supports_intgridgroup = []
_supports_ldtk = [] _supports_ldtk = []
_supports_worlds = []
_optional_tile_list = [] _optional_tile_list = []
no_panic = [] no_panic = []
[dependencies] [dependencies]
bevy = { optional = true, version = "0.15", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset", "serialize"] } bevy = { optional = true, version = "0.15", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset", "serialize"] }
anyhow = "1.0"
thiserror = "2.0"
log = "0.4" log = "0.4"
thiserror = "2.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
num-traits = "0.2" num-traits = "0.2"
...@@ -47,3 +48,5 @@ quadtree_rs = "0.1" ...@@ -47,3 +48,5 @@ quadtree_rs = "0.1"
micro_autotile = { version = "0.2", optional = true } micro_autotile = { version = "0.2", optional = true }
glam = { version = "0.29", features = ["serde"] } glam = { version = "0.29", features = ["serde"] }
[dev-dependencies]
test-case = "3.3.1"
\ No newline at end of file
...@@ -19,9 +19,10 @@ pub fn handle_ldtk_project_events( ...@@ -19,9 +19,10 @@ pub fn handle_ldtk_project_events(
if let Some(project) = assets.get(*id) { if let Some(project) = assets.get(*id) {
for level in project.get_all_levels() { for level in project.get_all_levels() {
if level.external_rel_path.is_none() { if level.external_rel_path.is_none() {
level_index.insert(
level_index level.identifier.clone(),
.insert(level.identifier.clone(), LdtkLevel::from_project(&project, level.clone())); LdtkLevel::from_project(project, level.clone()),
);
update_events.send(LevelDataUpdated(level.identifier.clone())); update_events.send(LevelDataUpdated(level.identifier.clone()));
} }
} }
......
...@@ -34,7 +34,7 @@ pub struct CameraBounder<'w, 's, Filter: QueryFilter + 'static> { ...@@ -34,7 +34,7 @@ pub struct CameraBounder<'w, 's, Filter: QueryFilter + 'static> {
map_query: MapQuery<'w>, map_query: MapQuery<'w>,
query: Query<'w, 's, &'static OrthographicProjection, Filter>, query: Query<'w, 's, &'static OrthographicProjection, Filter>,
} }
impl<'w, 's, Filter: QueryFilter + 'static> CameraBounder<'w, 's, Filter> { impl<Filter: QueryFilter + 'static> CameraBounder<'_, '_, Filter> {
pub fn get_extremeties(&self) -> Option<Rect> { pub fn get_extremeties(&self) -> Option<Rect> {
if let Some(bounds) = self.map_query.get_camera_bounds() { if let Some(bounds) = self.map_query.get_camera_bounds() {
if let Ok(proj) = self.query.get_single() { if let Ok(proj) = self.query.get_single() {
......
...@@ -19,7 +19,6 @@ mod data_1_4_0; ...@@ -19,7 +19,6 @@ mod data_1_4_0;
#[cfg(any(feature = "ldtk_1_5_3"))] #[cfg(any(feature = "ldtk_1_5_3"))]
mod data_1_5_3; mod data_1_5_3;
use crate::ldtk; use crate::ldtk;
#[cfg(feature = "ldtk_1_0_0")] #[cfg(feature = "ldtk_1_0_0")]
pub use data_1_0_0::*; pub use data_1_0_0::*;
...@@ -79,8 +78,8 @@ macro_rules! impl_from_bytes { ...@@ -79,8 +78,8 @@ macro_rules! impl_from_bytes {
}; };
} }
impl<'a> LdtkFromBytes<'a> for Level {} impl LdtkFromBytes<'_> for Level {}
impl<'a> LdtkFromBytes<'a> for Project {} impl LdtkFromBytes<'_> for Project {}
impl_from_bytes!(Level); impl_from_bytes!(Level);
impl_from_bytes!(Project); impl_from_bytes!(Project);
...@@ -89,11 +88,12 @@ impl_from_bytes!(Project); ...@@ -89,11 +88,12 @@ impl_from_bytes!(Project);
mod _bevy_impl { mod _bevy_impl {
use super::*; use super::*;
use bevy::asset::io::Reader; use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, AsyncReadExt, LoadContext, UntypedAssetId, VisitAssetDependencies}; use bevy::asset::{
AssetLoader, AsyncReadExt, LoadContext, UntypedAssetId, VisitAssetDependencies,
};
use bevy::prelude::{Asset, Handle}; use bevy::prelude::{Asset, Handle};
use bevy::reflect::TypePath; use bevy::reflect::TypePath;
impl TypePath for Project { impl TypePath for Project {
fn type_path() -> &'static str { fn type_path() -> &'static str {
"micro_ldtk::ldtk::Project" "micro_ldtk::ldtk::Project"
...@@ -124,7 +124,6 @@ mod _bevy_impl { ...@@ -124,7 +124,6 @@ mod _bevy_impl {
impl Asset for Level {} impl Asset for Level {}
#[derive(Asset, TypePath)] #[derive(Asset, TypePath)]
pub struct LevelSet(pub Vec<Handle<Level>>); pub struct LevelSet(pub Vec<Handle<Level>>);
...@@ -149,12 +148,6 @@ mod _bevy_impl { ...@@ -149,12 +148,6 @@ mod _bevy_impl {
.levels .levels
.iter() .iter()
.flat_map(|level| { .flat_map(|level| {
log::debug!(
"Checking if level is external: {} [{}]",
level.identifier,
level.external_rel_path.is_some()
);
level level
.external_rel_path .external_rel_path
.as_ref() .as_ref()
...@@ -210,7 +203,7 @@ mod _bevy_impl { ...@@ -210,7 +203,7 @@ mod _bevy_impl {
} }
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use _bevy_impl::{LdtkLoader, LdtkLevelLoader, LevelSet}; pub use _bevy_impl::{LdtkLevelLoader, LdtkLoader, LevelSet};
impl Project { impl Project {
pub fn get_all_levels(&self) -> Vec<&Level> { pub fn get_all_levels(&self) -> Vec<&Level> {
...@@ -224,24 +217,12 @@ impl Project { ...@@ -224,24 +217,12 @@ impl Project {
} }
} }
#[cfg(any( #[cfg(not(feature = "_supports_worlds"))]
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> { pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
vec![] vec![]
} }
#[cfg(any(feature = "ldtk_1_3_0", feature = "ldtk_1_4_0", feature = "ldtk_1_4_1"))] #[cfg(feature = "_supports_worlds")]
pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> { pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
let id = identifier.to_string(); let id = identifier.to_string();
self.worlds self.worlds
...@@ -264,7 +245,6 @@ pub enum LdtkLoadError { ...@@ -264,7 +245,6 @@ pub enum LdtkLoadError {
pub type LdtkProject = Project; pub type LdtkProject = Project;
#[cfg(feature = "autotile")] #[cfg(feature = "autotile")]
mod autotile_support { mod autotile_support {
use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus}; use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus};
...@@ -296,21 +276,15 @@ mod autotile_support { ...@@ -296,21 +276,15 @@ mod autotile_support {
1 => Some(AutoTileRule { 1 => Some(AutoTileRule {
chance: rule.chance as f32, chance: rule.chance as f32,
output: create_output(rule), output: create_output(rule),
matcher: TileMatcher::single(TileStatus::from( matcher: TileMatcher::single(TileStatus::from(rule.pattern[0])),
rule.pattern[0],
)),
}), }),
_ => { _ => TileMatcher::try_from(rule.pattern.as_slice())
TileMatcher::try_from(rule.pattern.as_slice()) .ok()
.ok().map(|matcher| { .map(|matcher| AutoTileRule {
AutoTileRule { matcher,
matcher, chance: rule.chance as f32,
chance: rule.chance as f32, output: create_output(rule),
output: create_output(rule), }),
}
})
}
}) })
.collect(); .collect();
...@@ -322,9 +296,12 @@ mod autotile_support { ...@@ -322,9 +296,12 @@ mod autotile_support {
fn from(value: &Project) -> Self { fn from(value: &Project) -> Self {
let mut base_set = AutoRuleSet::default(); let mut base_set = AutoRuleSet::default();
for layers in value.defs.layers.iter() { #[cfg(feature = "_supports_ui_tags")]
for rule_group in layers.auto_rule_groups.iter() { {
base_set = base_set + rule_group.into(); for layers in value.defs.layers.iter() {
for rule_group in layers.auto_rule_groups.iter() {
base_set = base_set + rule_group.into();
}
} }
} }
......
...@@ -66,7 +66,7 @@ mod __plugin { ...@@ -66,7 +66,7 @@ mod __plugin {
impl<CameraFilter: QueryFilter + Send + Sync + 'static> MicroLDTKCameraPlugin<CameraFilter> { impl<CameraFilter: QueryFilter + Send + Sync + 'static> MicroLDTKCameraPlugin<CameraFilter> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
_p: PhantomData::default(), _p: PhantomData,
} }
} }
} }
...@@ -93,19 +93,19 @@ mod __plugin { ...@@ -93,19 +93,19 @@ mod __plugin {
} }
} }
use std::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin}; pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin};
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use assets::{LevelIndex, TileMetadata, TilesetIndex}; pub use assets::{LevelIndex, TileMetadata, TilesetIndex};
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use camera::CameraBounder; pub use camera::CameraBounder;
pub use ldtk::{LdtkProject, Level, AutoLayerRuleGroup};
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use ldtk::LdtkLoader; pub use ldtk::LdtkLoader;
pub use ldtk::{AutoLayerRuleGroup, LdtkProject, Level};
pub use map_query::{CameraBounds, InstanceRef, MapQuery}; pub use map_query::{CameraBounds, InstanceRef, MapQuery};
#[cfg(feature = "autotile")] #[cfg(feature = "autotile")]
pub use micro_autotile as autotile; pub use micro_autotile as autotile;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use pregen::{write_layer_to_texture, write_map_to_texture, Rasterise}; pub use pregen::{write_layer_to_texture, write_map_to_texture, Rasterise};
use std::sync::atomic::{AtomicU32, Ordering};
pub use system::*; pub use system::*;
...@@ -114,7 +114,7 @@ impl<'a> InstanceRef<'a> { ...@@ -114,7 +114,7 @@ impl<'a> InstanceRef<'a> {
} }
} }
impl<'a, T: ToString> Index<T> for InstanceRef<'a> { impl<T: ToString> Index<T> for InstanceRef<'_> {
type Output = serde_json::Value; type Output = serde_json::Value;
fn index(&self, index: T) -> &Self::Output { fn index(&self, index: T) -> &Self::Output {
...@@ -152,7 +152,7 @@ impl CameraBounds { ...@@ -152,7 +152,7 @@ impl CameraBounds {
} }
} }
impl<'w> MapQuery<'w> { impl MapQuery<'_> {
// --- We put our logic in static accessors because we might source a level other // --- We put our logic in static accessors because we might source a level other
// --- than the currently active one. 'active' methods are a convenience to // --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is // --- call the static accessors on whatever the current level is
......
...@@ -9,230 +9,230 @@ pub struct MapQuery {} ...@@ -9,230 +9,230 @@ pub struct MapQuery {}
#[derive(Clone)] #[derive(Clone)]
pub struct InstanceRef<'a> { pub struct InstanceRef<'a> {
pub entity: &'a EntityInstance, pub entity: &'a EntityInstance,
} }
impl<'a> InstanceRef<'a> { impl<'a> InstanceRef<'a> {
/// Get the leftmost pixel of this entity's anchor point /// Get the leftmost pixel of this entity's anchor point
pub fn x(&self) -> i64 { pub fn x(&self) -> i64 {
self.entity.px[0] self.entity.px[0]
} }
/// Get the topmost pixel of this entity's anchor point /// Get the topmost pixel of this entity's anchor point
pub fn y(&self) -> i64 { pub fn y(&self) -> i64 {
self.entity.px[1] self.entity.px[1]
} }
/// Get the pixel width of this entity /// Get the pixel width of this entity
pub fn width(&self) -> i64 { pub fn width(&self) -> i64 {
self.entity.width self.entity.width
} }
/// Get the pixel width of this entity /// Get the pixel width of this entity
pub fn height(&self) -> i64 { pub fn height(&self) -> i64 {
self.entity.height self.entity.height
} }
/// Get the category that this instance belongs to. Exactly matches the string name /// Get the category that this instance belongs to. Exactly matches the string name
/// found in the LDTK entities list /// found in the LDTK entities list
pub fn get_type(&self) -> &'a String { pub fn get_type(&self) -> &'a String {
&self.entity.identifier &self.entity.identifier
} }
/// Try to get a type safe representation of this entity's type, as long as the target type /// Try to get a type safe representation of this entity's type, as long as the target type
/// can be produced from a [str] representation /// can be produced from a [str] representation
/// ///
/// ## Example /// ## Example
/// ///
/// ``` /// ```
/// # use std::str::FromStr; /// # use std::str::FromStr;
/// # use micro_ldtk::InstanceRef; /// # use micro_ldtk::InstanceRef;
/// # use micro_ldtk::ldtk::EntityInstance; /// # use micro_ldtk::ldtk::EntityInstance;
/// ///
/// #[derive(PartialEq, Debug)] /// #[derive(PartialEq, Debug)]
/// enum MyEntityType { /// enum MyEntityType {
/// Player, /// Player,
/// Monster, /// Monster,
/// } /// }
/// ///
/// impl FromStr for MyEntityType { /// impl FromStr for MyEntityType {
/// # type Err = (); /// # type Err = ();
/// fn from_str(s: &str) -> Result<Self, Self::Err> { /// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// match s { /// match s {
/// "player" => Ok(Self::Player), /// "player" => Ok(Self::Player),
/// "monster" => Ok(Self::Monster), /// "monster" => Ok(Self::Monster),
/// # _ => panic!("Oh no") /// # _ => panic!("Oh no")
/// } /// }
/// } /// }
/// } /// }
/// ///
/// let data_from_ldtk: EntityInstance = EntityInstance { /// let data_from_ldtk: EntityInstance = EntityInstance {
/// identifier: "player".to_string(), /// identifier: "player".to_string(),
/// // ...other properties /// // ...other properties
/// # smart_color: "".to_string(), /// # smart_color: "".to_string(),
/// # grid: vec![], /// # grid: vec![],
/// # pivot: vec![], /// # pivot: vec![],
/// # tags: vec![], /// # tags: vec![],
/// # tile: None, /// # tile: None,
/// # world_x: None, /// # world_x: None,
/// # world_y: None, /// # world_y: None,
/// # def_uid: 0, /// # def_uid: 0,
/// # field_instances: vec![], /// # field_instances: vec![],
/// # height: 0, /// # height: 0,
/// # iid: "".to_string(), /// # iid: "".to_string(),
/// # px: vec![], /// # px: vec![],
/// # width: 0, /// # width: 0,
/// }; /// };
/// # /// #
/// # let process_ldtk_data = || -> InstanceRef<'_> { /// # let process_ldtk_data = || -> InstanceRef<'_> {
/// # InstanceRef { /// # InstanceRef {
/// # entity: &data_from_ldtk, /// # entity: &data_from_ldtk,
/// # } /// # }
/// # }; /// # };
/// ///
/// let my_entity_type: InstanceRef<'_> = process_ldtk_data(); /// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
/// assert_eq!(my_entity_type.try_get_typed_id(), Ok(MyEntityType::Player)); /// assert_eq!(my_entity_type.try_get_typed_id(), Ok(MyEntityType::Player));
/// ``` /// ```
pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> { pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> {
T::from_str(self.get_type().as_str()) T::from_str(self.get_type().as_str())
} }
/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null] /// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
/// if there is no property with the given name /// if there is no property with the given name
pub fn property(&self, name: impl ToString) -> serde_json::Value { pub fn property(&self, name: impl ToString) -> serde_json::Value {
self[name].clone() self[name].clone()
} }
/// Get a reference to the inner instance of this instance ref /// Get a reference to the inner instance of this instance ref
pub fn instance_ref(&self) -> &EntityInstance { pub fn instance_ref(&self) -> &EntityInstance {
self.entity self.entity
} }
} }
impl<'a, T: ToString> Index<T> for InstanceRef<'a> { impl<T: ToString> Index<T> for InstanceRef<'_> {
type Output = serde_json::Value; type Output = serde_json::Value;
fn index(&self, index: T) -> &Self::Output { fn index(&self, index: T) -> &Self::Output {
let name = index.to_string(); let name = index.to_string();
for field in &self.entity.field_instances { for field in &self.entity.field_instances {
if field.identifier == name { if field.identifier == name {
return field.value.as_ref().unwrap_or(&serde_json::Value::Null); return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
} }
} }
&serde_json::Value::Null &serde_json::Value::Null
} }
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct CameraBounds { pub struct CameraBounds {
pub left: f32, pub left: f32,
pub top: f32, pub top: f32,
pub bottom: f32, pub bottom: f32,
pub right: f32, pub right: f32,
} }
impl CameraBounds { impl CameraBounds {
pub fn get_min_x(&self, camera_width: f32) -> f32 { pub fn get_min_x(&self, camera_width: f32) -> f32 {
self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0) self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
} }
pub fn get_max_x(&self, camera_width: f32) -> f32 { pub fn get_max_x(&self, camera_width: f32) -> f32 {
self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0) self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
} }
pub fn get_min_y(&self, camera_height: f32) -> f32 { pub fn get_min_y(&self, camera_height: f32) -> f32 {
self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0) self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
} }
pub fn get_max_y(&self, camera_height: f32) -> f32 { pub fn get_max_y(&self, camera_height: f32) -> f32 {
self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0) self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
} }
} }
impl MapQuery { impl MapQuery {
// --- We put our logic in static accessors because we might source a level other // --- We put our logic in static accessors because we might source a level other
// --- than the currently active one. 'active' methods are a convenience to // --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is // --- call the static accessors on whatever the current level is
/// Perform an action on each layer of the given LDTK level /// Perform an action on each layer of the given LDTK level
pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) { pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
for layer in level.layers() { for layer in level.layers() {
cb(layer); cb(layer);
} }
} }
/// Retrieve an iterator over every layer in the given level, regardless of type /// Retrieve an iterator over every layer in the given level, regardless of type
pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> { pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
level.layers() level.layers()
} }
/// Retrieve a reference to every entity stored in the given level, regardless of which layer it is found on /// Retrieve a reference to every entity stored in the given level, regardless of which layer it is found on
pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> { pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> {
level level
.layers() .layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter()) .flat_map(|layer| layer.as_ref().entity_instances.iter())
.collect() .collect()
} }
/// Retrieve an enhanced wrapper to every entity stored in the given level, regardless of which layer it is found on /// Retrieve an enhanced wrapper to every entity stored in the given level, regardless of which layer it is found on
pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> { pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> {
level level
.layers() .layers()
.flat_map(|layer| { .flat_map(|layer| {
layer layer
.as_ref() .as_ref()
.entity_instances .entity_instances
.iter() .iter()
.map(|inst| InstanceRef { entity: inst }) .map(|inst| InstanceRef { entity: inst })
}) })
.collect() .collect()
} }
/// Retrieve a reference to every entity stored in the given level that matches the specified type name. /// Retrieve a reference to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list /// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_entities_of( pub fn get_filtered_entities_of(
level: &LdtkLevel, level: &LdtkLevel,
entity_type: impl ToString, entity_type: impl ToString,
) -> Vec<&EntityInstance> { ) -> Vec<&EntityInstance> {
let e_type = entity_type.to_string(); let e_type = entity_type.to_string();
level level
.layers() .layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter()) .flat_map(|layer| layer.as_ref().entity_instances.iter())
.filter(|inst| inst.identifier == e_type) .filter(|inst| inst.identifier == e_type)
.collect() .collect()
} }
/// Retrieve an enhanced wrapper to every entity stored in the given level that matches the specified type name. /// Retrieve an enhanced wrapper to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list /// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_instance_refs_of( pub fn get_filtered_instance_refs_of(
level: &LdtkLevel, level: &LdtkLevel,
entity_type: impl ToString, entity_type: impl ToString,
) -> Vec<InstanceRef> { ) -> Vec<InstanceRef> {
let e_type = entity_type.to_string(); let e_type = entity_type.to_string();
level level
.layers() .layers()
.flat_map(|layer| { .flat_map(|layer| {
layer layer
.as_ref() .as_ref()
.entity_instances .entity_instances
.iter() .iter()
.map(|inst| InstanceRef { entity: inst }) .map(|inst| InstanceRef { entity: inst })
}) })
.filter(|inst| inst.entity.identifier == e_type) .filter(|inst| inst.entity.identifier == e_type)
.collect() .collect()
} }
/// Retrieve an owned copy of all entity data in the given level /// Retrieve an owned copy of all entity data in the given level
pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> { pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
level level
.layers() .layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter().cloned()) .flat_map(|layer| layer.as_ref().entity_instances.iter().cloned())
.collect() .collect()
} }
/// Use the size of the level to create a zero-based rectangle indicating the boundaries that a camera should /// Use the size of the level to create a zero-based rectangle indicating the boundaries that a camera should
///stay within to avoid showing any out-of-level space ///stay within to avoid showing any out-of-level space
pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds { pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
let level = level.level_ref(); let level = level.level_ref();
CameraBounds { CameraBounds {
left: 0.0, left: 0.0,
top: level.px_hei as f32, top: level.px_hei as f32,
bottom: 0.0, bottom: 0.0,
right: level.px_wid as f32, right: level.px_wid as f32,
} }
} }
} }
use bevy::prelude::{Image, TextureAtlas};
use bevy::render::render_resource::TextureFormat;
use bevy::image::TextureFormatPixelInfo; use bevy::image::TextureFormatPixelInfo;
use bevy::prelude::Image;
use bevy::render::render_resource::TextureFormat;
use bevy::sprite::TextureAtlasLayout; use bevy::sprite::TextureAtlasLayout;
use crate::{ use crate::{
......
...@@ -18,8 +18,8 @@ use serde_json::{Map, Number, Value}; ...@@ -18,8 +18,8 @@ use serde_json::{Map, Number, Value};
use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, Project, TileInstance}; use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, Project, TileInstance};
use crate::system::Indexer; use crate::system::Indexer;
use crate::{get_ldtk_tile_scale, px_to_grid};
use crate::MapQuery; use crate::MapQuery;
use crate::{get_ldtk_tile_scale, px_to_grid};
mod compat; mod compat;
...@@ -73,7 +73,7 @@ impl<'a> TileRef<'a> { ...@@ -73,7 +73,7 @@ impl<'a> TileRef<'a> {
} }
} }
impl<'a> Debug for TileRef<'a> { impl Debug for TileRef<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TileRef") f.debug_struct("TileRef")
.field("gid", &self.gid()) .field("gid", &self.gid())
...@@ -119,13 +119,19 @@ pub struct LdtkLevel { ...@@ -119,13 +119,19 @@ pub struct LdtkLevel {
impl LdtkLevel { impl LdtkLevel {
pub fn from_project(project: &Project, level: Level) -> Self { pub fn from_project(project: &Project, level: Level) -> Self {
let mut level_data = LdtkLevel::from(level); let mut level_data = LdtkLevel::from(level);
level_data.layers_mut().for_each(|layer| { #[cfg(feature = "_supports_ui_tags")]
if let Some(def) = project.defs.layers.iter().find(|inner| { {
inner.uid == layer.layer.layer_def_uid level_data.layers_mut().for_each(|layer| {
}) { if let Some(def) = project
layer.tags = def.ui_filter_tags.clone(); .defs
} .layers
}); .iter()
.find(|inner| inner.uid == layer.layer.layer_def_uid)
{
layer.tags = def.ui_filter_tags.clone();
}
});
}
level_data level_data
} }
pub fn width(&self) -> f32 { pub fn width(&self) -> f32 {
...@@ -173,7 +179,6 @@ impl LdtkLevel { ...@@ -173,7 +179,6 @@ impl LdtkLevel {
pub fn get_indexer(&self) -> Indexer { pub fn get_indexer(&self) -> Indexer {
Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei)) Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei))
} }
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
......
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
use bevy::prelude::{Component, IVec2, Resource}; use bevy::prelude::{Component, Resource};
use glam::IVec2;
use num_traits::AsPrimitive; use num_traits::AsPrimitive;
use crate::get_ldtk_tile_scale; use crate::get_ldtk_tile_scale;
...@@ -19,11 +20,11 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) { ...@@ -19,11 +20,11 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) {
(x as f32, y as f32) (x as f32, y as f32)
} }
#[cfg_attr(feature="bevy", derive(Component))] #[cfg_attr(feature = "bevy", derive(Component))]
pub struct WorldLinked; pub struct WorldLinked;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
#[cfg_attr(feature="bevy", derive(Resource))] #[cfg_attr(feature = "bevy", derive(Resource))]
pub struct ActiveLevel { pub struct ActiveLevel {
pub map: String, pub map: String,
pub dirty: bool, pub dirty: bool,
...@@ -53,7 +54,10 @@ impl Indexer { ...@@ -53,7 +54,10 @@ impl Indexer {
} }
pub fn index(&self, x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize { pub fn index(&self, x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize {
((y.as_() * self.width) + x.as_()).as_() match (self.width(), self.height()) {
(0, _) | (_, 0) => 0,
(w, _) => ((y.as_() * w) + x.as_()).as_(),
}
} }
pub fn index_checked( pub fn index_checked(
...@@ -69,10 +73,14 @@ impl Indexer { ...@@ -69,10 +73,14 @@ impl Indexer {
} }
pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) { pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) {
( let index = index.as_();
(index.as_() % self.width).max(0) as usize, match index {
(index.as_() / self.width).max(0) as usize, 0 => (0, 0),
) _ => (
index.checked_rem(self.width).unwrap_or(0) as usize,
index.checked_div(self.width).unwrap_or(0) as usize,
),
}
} }
pub fn width(&self) -> i64 { pub fn width(&self) -> i64 {
...@@ -89,7 +97,6 @@ impl Indexer { ...@@ -89,7 +97,6 @@ impl Indexer {
x >= 0 && x < self.width && y >= 0 && y < self.height x >= 0 && x < self.width && y >= 0 && y < self.height
} }
#[cfg(feature = "bevy")]
/// Perform a transformation to flip a grid point (top down coordinates) into a render /// Perform a transformation to flip a grid point (top down coordinates) into a render
/// point (bottom up coordinates) /// point (bottom up coordinates)
pub fn flip_y(&self, point: IVec2) -> IVec2 { pub fn flip_y(&self, point: IVec2) -> IVec2 {
......
use glam::IVec2;
use micro_ldtk::Indexer;
use num_traits::AsPrimitive;
use test_case::test_case;
#[test_case(0, 0 => 0)]
#[test_case(4, 0 => 4)]
#[test_case(0, 3 => 15)]
#[test_case(4, 3 => 19)]
#[test_case(2, 2 => 12)]
#[test_case(1i32, 1i32 => 6)]
#[test_case(2.5f32, 1.2f32 => 7)] // Floating point coordinates are truncated
fn test_indexer_calculates_correct_index(
x: impl AsPrimitive<i64>,
y: impl AsPrimitive<i64>,
) -> usize {
let indexer = Indexer::new(5, 4);
indexer.index(x, y)
}
#[test_case(0 => (0, 0))]
#[test_case(4 => (4, 0))]
#[test_case(5 => (0, 1))]
#[test_case(9 => (4, 1))]
#[test_case(10 => (0, 2))]
#[test_case(14 => (4, 2))]
fn test_indexer_reverse(index: usize) -> (usize, usize) {
let indexer = Indexer::new(5, 3);
indexer.reverse(index)
}
#[test_case(0, 0)]
#[test_case(4, 0)]
#[test_case(0, 1)]
#[test_case(2, 2)]
#[test_case(4, 2)]
fn test_index_is_reflexive(x: usize, y: usize) {
let indexer = Indexer::new(5, 3);
let idx = indexer.index(x, y);
let (x_rev, y_rev) = indexer.reverse(idx);
assert_eq!((x, y), (x_rev, y_rev));
}
#[test_case(5, -1)]
#[test_case(5, 10)]
#[test_case(10, 10)]
#[test_case(10, 3)]
#[test_case(-12, 3)]
#[test_case(-00, 123_000_000)]
fn test_indexer_index_checked_returns_none_for_out_of_bounds(x: i64, y: i64) {
let indexer = Indexer::new(10, 10);
assert_eq!(indexer.index_checked(x, y), None);
}
#[test]
fn test_indexer_with_zero_dimensions() {
// Create an indexer with zero width
let zero_width_indexer = Indexer::new(0, 10);
assert_eq!(zero_width_indexer.width(), 0);
assert_eq!(zero_width_indexer.height(), 10);
// Zero width should make all x coordinates invalid
assert!(!zero_width_indexer.is_valid(0, 5));
assert_eq!(zero_width_indexer.index_checked(0, 5), None);
// Index calculation with zero width
assert_eq!(zero_width_indexer.index(2, 3), 0); // 3 * 0 + 2 = 0
// Reverse calculation with zero width
let (x, y) = zero_width_indexer.reverse(5);
assert_eq!(x, 0); // Any index % 0 is treated as 0 due to .max(0)
assert_eq!(y, 0); // Any index / 0 is treated as 0 due to .max(0)
// Create an indexer with zero height
let zero_height_indexer = Indexer::new(10, 0);
assert_eq!(zero_height_indexer.width(), 10);
assert_eq!(zero_height_indexer.height(), 0);
// Zero height should make all y coordinates invalid
assert!(!zero_height_indexer.is_valid(5, 0));
assert_eq!(zero_height_indexer.index_checked(5, 0), None);
// Test flip_y with zero height
let flipped = zero_height_indexer.flip_y(IVec2::new(5, 3));
assert_eq!(flipped, IVec2::new(5, -3)); // 0 - 3 = -3
}