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 = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "approx"
version = "0.5.1"
......@@ -2235,9 +2229,8 @@ dependencies = [
[[package]]
name = "micro_ldtk"
version = "0.14.0"
version = "0.14.1"
dependencies = [
"anyhow",
"bevy",
"glam",
"log",
......@@ -2246,6 +2239,7 @@ dependencies = [
"quadtree_rs",
"serde",
"serde_json",
"test-case",
"thiserror 2.0.12",
]
......@@ -3379,6 +3373,39 @@ dependencies = [
"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]]
name = "thiserror"
version = "1.0.69"
......
[package]
name = "micro_ldtk"
version = "0.14.0"
version = "0.14.1"
edition = "2021"
authors = [
......@@ -12,10 +12,10 @@ license = "Apache-2.0"
[features]
default = ["ldtk_1_5_3", "autotile"]
ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list"]
ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup"]
ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup"]
ldtk_1_3_0 = ["_supports_ldtk"]
ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list", "_supports_ui_tags", "_supports_worlds"]
ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
ldtk_1_3_0 = ["_supports_ldtk", "_supports_worlds"]
ldtk_1_2_5 = ["_supports_ldtk"]
ldtk_1_2_4 = ["_supports_ldtk"]
ldtk_1_2_3 = ["_supports_ldtk"]
......@@ -29,17 +29,18 @@ ldtk_1_1_0 = ["_supports_ldtk"]
ldtk_1_0_0 = ["_supports_ldtk"]
autotile = ["dep:micro_autotile"]
bevy = ["dep:bevy"]
_supports_ui_tags = []
_supports_intgridgroup = []
_supports_ldtk = []
_supports_worlds = []
_optional_tile_list = []
no_panic = []
[dependencies]
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"
thiserror = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
num-traits = "0.2"
......@@ -47,3 +48,5 @@ quadtree_rs = "0.1"
micro_autotile = { version = "0.2", optional = true }
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(
if let Some(project) = assets.get(*id) {
for level in project.get_all_levels() {
if level.external_rel_path.is_none() {
level_index
.insert(level.identifier.clone(), LdtkLevel::from_project(&project, level.clone()));
level_index.insert(
level.identifier.clone(),
LdtkLevel::from_project(project, level.clone()),
);
update_events.send(LevelDataUpdated(level.identifier.clone()));
}
}
......
......@@ -34,7 +34,7 @@ pub struct CameraBounder<'w, 's, Filter: QueryFilter + 'static> {
map_query: MapQuery<'w>,
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> {
if let Some(bounds) = self.map_query.get_camera_bounds() {
if let Ok(proj) = self.query.get_single() {
......
......@@ -19,7 +19,6 @@ mod data_1_4_0;
#[cfg(any(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::*;
......@@ -79,8 +78,8 @@ macro_rules! impl_from_bytes {
};
}
impl<'a> LdtkFromBytes<'a> for Level {}
impl<'a> LdtkFromBytes<'a> for Project {}
impl LdtkFromBytes<'_> for Level {}
impl LdtkFromBytes<'_> for Project {}
impl_from_bytes!(Level);
impl_from_bytes!(Project);
......@@ -89,11 +88,12 @@ impl_from_bytes!(Project);
mod _bevy_impl {
use super::*;
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::reflect::TypePath;
impl TypePath for Project {
fn type_path() -> &'static str {
"micro_ldtk::ldtk::Project"
......@@ -124,7 +124,6 @@ mod _bevy_impl {
impl Asset for Level {}
#[derive(Asset, TypePath)]
pub struct LevelSet(pub Vec<Handle<Level>>);
......@@ -149,12 +148,6 @@ mod _bevy_impl {
.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()
......@@ -210,7 +203,7 @@ mod _bevy_impl {
}
#[cfg(feature = "bevy")]
pub use _bevy_impl::{LdtkLoader, LdtkLevelLoader, LevelSet};
pub use _bevy_impl::{LdtkLevelLoader, LdtkLoader, LevelSet};
impl Project {
pub fn get_all_levels(&self) -> Vec<&Level> {
......@@ -224,24 +217,12 @@ impl Project {
}
}
#[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"
))]
#[cfg(not(feature = "_supports_worlds"))]
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"))]
#[cfg(feature = "_supports_worlds")]
pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
let id = identifier.to_string();
self.worlds
......@@ -264,7 +245,6 @@ pub enum LdtkLoadError {
pub type LdtkProject = Project;
#[cfg(feature = "autotile")]
mod autotile_support {
use micro_autotile::{AutoRuleSet, AutoTileRule, TileMatcher, TileOutput, TileStatus};
......@@ -296,21 +276,15 @@ mod autotile_support {
1 => Some(AutoTileRule {
chance: rule.chance as f32,
output: create_output(rule),
matcher: TileMatcher::single(TileStatus::from(
rule.pattern[0],
)),
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),
}
})
}
_ => TileMatcher::try_from(rule.pattern.as_slice())
.ok()
.map(|matcher| AutoTileRule {
matcher,
chance: rule.chance as f32,
output: create_output(rule),
}),
})
.collect();
......@@ -322,9 +296,12 @@ mod autotile_support {
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();
#[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();
}
}
}
......
......@@ -66,7 +66,7 @@ mod __plugin {
impl<CameraFilter: QueryFilter + Send + Sync + 'static> MicroLDTKCameraPlugin<CameraFilter> {
pub fn new() -> Self {
Self {
_p: PhantomData::default(),
_p: PhantomData,
}
}
}
......@@ -93,19 +93,19 @@ mod __plugin {
}
}
use std::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "bevy")]
pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin};
#[cfg(feature = "bevy")]
pub use assets::{LevelIndex, TileMetadata, TilesetIndex};
#[cfg(feature = "bevy")]
pub use camera::CameraBounder;
pub use ldtk::{LdtkProject, Level, AutoLayerRuleGroup};
#[cfg(feature = "bevy")]
pub use ldtk::LdtkLoader;
pub use ldtk::{AutoLayerRuleGroup, LdtkProject, Level};
pub use map_query::{CameraBounds, InstanceRef, MapQuery};
#[cfg(feature = "autotile")]
pub use micro_autotile as autotile;
#[cfg(feature = "bevy")]
pub use pregen::{write_layer_to_texture, write_map_to_texture, Rasterise};
use std::sync::atomic::{AtomicU32, Ordering};
pub use system::*;
......@@ -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;
fn index(&self, index: T) -> &Self::Output {
......@@ -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
// --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is
......
......@@ -9,230 +9,230 @@ pub struct MapQuery {}
#[derive(Clone)]
pub struct InstanceRef<'a> {
pub entity: &'a EntityInstance,
pub entity: &'a EntityInstance,
}
impl<'a> InstanceRef<'a> {
/// Get the leftmost pixel of this entity's anchor point
pub fn x(&self) -> i64 {
self.entity.px[0]
}
/// Get the topmost pixel of this entity's anchor point
pub fn y(&self) -> i64 {
self.entity.px[1]
}
/// Get the pixel width of this entity
pub fn width(&self) -> i64 {
self.entity.width
}
/// Get the pixel width of this entity
pub fn height(&self) -> i64 {
self.entity.height
}
/// Get the category that this instance belongs to. Exactly matches the string name
/// found in the LDTK entities list
pub fn get_type(&self) -> &'a String {
&self.entity.identifier
}
/// 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
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use micro_ldtk::InstanceRef;
/// # use micro_ldtk::ldtk::EntityInstance;
///
/// #[derive(PartialEq, Debug)]
/// enum MyEntityType {
/// Player,
/// Monster,
/// }
///
/// impl FromStr for MyEntityType {
/// # type Err = ();
/// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// match s {
/// "player" => Ok(Self::Player),
/// "monster" => Ok(Self::Monster),
/// # _ => panic!("Oh no")
/// }
/// }
/// }
///
/// let data_from_ldtk: EntityInstance = EntityInstance {
/// identifier: "player".to_string(),
/// // ...other properties
/// # smart_color: "".to_string(),
/// # grid: vec![],
/// # pivot: vec![],
/// # tags: vec![],
/// # tile: None,
/// # world_x: None,
/// # world_y: None,
/// # def_uid: 0,
/// # field_instances: vec![],
/// # height: 0,
/// # iid: "".to_string(),
/// # px: vec![],
/// # width: 0,
/// };
/// #
/// # let process_ldtk_data = || -> InstanceRef<'_> {
/// # InstanceRef {
/// # entity: &data_from_ldtk,
/// # }
/// # };
///
/// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
/// 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> {
T::from_str(self.get_type().as_str())
}
/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
/// if there is no property with the given name
pub fn property(&self, name: impl ToString) -> serde_json::Value {
self[name].clone()
}
/// Get a reference to the inner instance of this instance ref
pub fn instance_ref(&self) -> &EntityInstance {
self.entity
}
/// Get the leftmost pixel of this entity's anchor point
pub fn x(&self) -> i64 {
self.entity.px[0]
}
/// Get the topmost pixel of this entity's anchor point
pub fn y(&self) -> i64 {
self.entity.px[1]
}
/// Get the pixel width of this entity
pub fn width(&self) -> i64 {
self.entity.width
}
/// Get the pixel width of this entity
pub fn height(&self) -> i64 {
self.entity.height
}
/// Get the category that this instance belongs to. Exactly matches the string name
/// found in the LDTK entities list
pub fn get_type(&self) -> &'a String {
&self.entity.identifier
}
/// 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
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use micro_ldtk::InstanceRef;
/// # use micro_ldtk::ldtk::EntityInstance;
///
/// #[derive(PartialEq, Debug)]
/// enum MyEntityType {
/// Player,
/// Monster,
/// }
///
/// impl FromStr for MyEntityType {
/// # type Err = ();
/// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// match s {
/// "player" => Ok(Self::Player),
/// "monster" => Ok(Self::Monster),
/// # _ => panic!("Oh no")
/// }
/// }
/// }
///
/// let data_from_ldtk: EntityInstance = EntityInstance {
/// identifier: "player".to_string(),
/// // ...other properties
/// # smart_color: "".to_string(),
/// # grid: vec![],
/// # pivot: vec![],
/// # tags: vec![],
/// # tile: None,
/// # world_x: None,
/// # world_y: None,
/// # def_uid: 0,
/// # field_instances: vec![],
/// # height: 0,
/// # iid: "".to_string(),
/// # px: vec![],
/// # width: 0,
/// };
/// #
/// # let process_ldtk_data = || -> InstanceRef<'_> {
/// # InstanceRef {
/// # entity: &data_from_ldtk,
/// # }
/// # };
///
/// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
/// 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> {
T::from_str(self.get_type().as_str())
}
/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
/// if there is no property with the given name
pub fn property(&self, name: impl ToString) -> serde_json::Value {
self[name].clone()
}
/// Get a reference to the inner instance of this instance ref
pub fn instance_ref(&self) -> &EntityInstance {
self.entity
}
}
impl<'a, T: ToString> Index<T> for InstanceRef<'a> {
type Output = serde_json::Value;
impl<T: ToString> Index<T> for InstanceRef<'_> {
type Output = serde_json::Value;
fn index(&self, index: T) -> &Self::Output {
let name = index.to_string();
for field in &self.entity.field_instances {
if field.identifier == name {
return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
}
}
fn index(&self, index: T) -> &Self::Output {
let name = index.to_string();
for field in &self.entity.field_instances {
if field.identifier == name {
return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
}
}
&serde_json::Value::Null
}
&serde_json::Value::Null
}
}
#[derive(Copy, Clone, Debug)]
pub struct CameraBounds {
pub left: f32,
pub top: f32,
pub bottom: f32,
pub right: f32,
pub left: f32,
pub top: f32,
pub bottom: f32,
pub right: f32,
}
impl CameraBounds {
pub fn get_min_x(&self, camera_width: f32) -> f32 {
self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_x(&self, camera_width: f32) -> f32 {
self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_min_y(&self, camera_height: f32) -> f32 {
self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_y(&self, camera_height: f32) -> f32 {
self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_min_x(&self, camera_width: f32) -> f32 {
self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_x(&self, camera_width: f32) -> f32 {
self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_min_y(&self, camera_height: f32) -> f32 {
self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_y(&self, camera_height: f32) -> f32 {
self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
}
impl MapQuery {
// --- 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
// --- call the static accessors on whatever the current level is
/// Perform an action on each layer of the given LDTK level
pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
for layer in level.layers() {
cb(layer);
}
}
/// Retrieve an iterator over every layer in the given level, regardless of type
pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
level.layers()
}
/// 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> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.collect()
}
/// 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> {
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.collect()
}
/// 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
pub fn get_filtered_entities_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<&EntityInstance> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.filter(|inst| inst.identifier == e_type)
.collect()
}
/// 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
pub fn get_filtered_instance_refs_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<InstanceRef> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.filter(|inst| inst.entity.identifier == e_type)
.collect()
}
/// Retrieve an owned copy of all entity data in the given level
pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter().cloned())
.collect()
}
/// 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
pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
let level = level.level_ref();
CameraBounds {
left: 0.0,
top: level.px_hei as f32,
bottom: 0.0,
right: level.px_wid as f32,
}
}
// --- 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
// --- call the static accessors on whatever the current level is
/// Perform an action on each layer of the given LDTK level
pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
for layer in level.layers() {
cb(layer);
}
}
/// Retrieve an iterator over every layer in the given level, regardless of type
pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
level.layers()
}
/// 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> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.collect()
}
/// 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> {
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.collect()
}
/// 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
pub fn get_filtered_entities_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<&EntityInstance> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.filter(|inst| inst.identifier == e_type)
.collect()
}
/// 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
pub fn get_filtered_instance_refs_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<InstanceRef> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.filter(|inst| inst.entity.identifier == e_type)
.collect()
}
/// Retrieve an owned copy of all entity data in the given level
pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter().cloned())
.collect()
}
/// 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
pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
let level = level.level_ref();
CameraBounds {
left: 0.0,
top: level.px_hei as f32,
bottom: 0.0,
right: level.px_wid as f32,
}
}
}
use bevy::prelude::{Image, TextureAtlas};
use bevy::render::render_resource::TextureFormat;
use bevy::image::TextureFormatPixelInfo;
use bevy::prelude::Image;
use bevy::render::render_resource::TextureFormat;
use bevy::sprite::TextureAtlasLayout;
use crate::{
......
......@@ -18,8 +18,8 @@ use serde_json::{Map, Number, Value};
use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, Project, TileInstance};
use crate::system::Indexer;
use crate::{get_ldtk_tile_scale, px_to_grid};
use crate::MapQuery;
use crate::{get_ldtk_tile_scale, px_to_grid};
mod compat;
......@@ -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 {
f.debug_struct("TileRef")
.field("gid", &self.gid())
......@@ -119,13 +119,19 @@ pub struct LdtkLevel {
impl LdtkLevel {
pub fn from_project(project: &Project, level: Level) -> Self {
let mut level_data = LdtkLevel::from(level);
level_data.layers_mut().for_each(|layer| {
if let Some(def) = project.defs.layers.iter().find(|inner| {
inner.uid == layer.layer.layer_def_uid
}) {
layer.tags = def.ui_filter_tags.clone();
}
});
#[cfg(feature = "_supports_ui_tags")]
{
level_data.layers_mut().for_each(|layer| {
if let Some(def) = project
.defs
.layers
.iter()
.find(|inner| inner.uid == layer.layer.layer_def_uid)
{
layer.tags = def.ui_filter_tags.clone();
}
});
}
level_data
}
pub fn width(&self) -> f32 {
......@@ -173,7 +179,6 @@ impl LdtkLevel {
pub fn get_indexer(&self) -> Indexer {
Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei))
}
}
#[derive(Debug, Clone, Default)]
......
#[cfg(feature = "bevy")]
use bevy::prelude::{Component, IVec2, Resource};
use bevy::prelude::{Component, Resource};
use glam::IVec2;
use num_traits::AsPrimitive;
use crate::get_ldtk_tile_scale;
......@@ -19,11 +20,11 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) {
(x as f32, y as f32)
}
#[cfg_attr(feature="bevy", derive(Component))]
#[cfg_attr(feature = "bevy", derive(Component))]
pub struct WorldLinked;
#[derive(Default, Clone, Debug)]
#[cfg_attr(feature="bevy", derive(Resource))]
#[cfg_attr(feature = "bevy", derive(Resource))]
pub struct ActiveLevel {
pub map: String,
pub dirty: bool,
......@@ -53,7 +54,10 @@ impl Indexer {
}
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(
......@@ -69,10 +73,14 @@ impl Indexer {
}
pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) {
(
(index.as_() % self.width).max(0) as usize,
(index.as_() / self.width).max(0) as usize,
)
let index = index.as_();
match index {
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 {
......@@ -89,7 +97,6 @@ impl Indexer {
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
/// point (bottom up coordinates)
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
}