use std::fmt::Debug; use std::ops::Index; use std::str::FromStr; use crate::ldtk::EntityInstance; use crate::{LdtkLayer, LdtkLevel}; pub struct MapQuery {} #[derive(Clone)] pub struct InstanceRef<'a> { 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 } } impl<'a, T: ToString> Index<T> for InstanceRef<'a> { 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); } } &serde_json::Value::Null } } #[derive(Copy, Clone, Debug)] pub struct CameraBounds { 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) } } 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, } } }