Skip to main content

micro_ldtk/
map_query_neutral.rs

1use std::fmt::Debug;
2use std::ops::Index;
3use std::str::FromStr;
4
5use crate::ldtk::EntityInstance;
6use crate::{LdtkLayer, LdtkLevel};
7
8pub struct MapQuery {}
9
10#[derive(Clone)]
11pub struct InstanceRef<'a> {
12	pub entity: &'a EntityInstance,
13}
14
15impl<'a> InstanceRef<'a> {
16	/// Get the leftmost pixel of this entity's anchor point
17	pub fn x(&self) -> i64 {
18		self.entity.px[0]
19	}
20	/// Get the topmost pixel of this entity's anchor point
21	pub fn y(&self) -> i64 {
22		self.entity.px[1]
23	}
24	/// Get the pixel width of this entity
25	pub fn width(&self) -> i64 {
26		self.entity.width
27	}
28	/// Get the pixel width of this entity
29	pub fn height(&self) -> i64 {
30		self.entity.height
31	}
32
33	/// Get the category that this instance belongs to. Exactly matches the string name
34	/// found in the LDTK entities list
35	pub fn get_type(&self) -> &'a String {
36		&self.entity.identifier
37	}
38	/// Try to get a type safe representation of this entity's type, as long as the target type
39	/// can be produced from a [str] representation
40	///
41	/// ## Example
42	///
43	/// ```
44	/// # use std::str::FromStr;
45	/// # use micro_ldtk::InstanceRef;
46	/// # use micro_ldtk::ldtk::EntityInstance;
47	///
48	/// #[derive(PartialEq, Debug)]
49	/// enum MyEntityType {
50	///     Player,
51	///     Monster,
52	/// }
53	///
54	/// impl FromStr for MyEntityType {
55	/// #    type Err = ();
56	///     fn from_str(s: &str) -> Result<Self, Self::Err> {
57	///         match s {
58	///             "player" => Ok(Self::Player),
59	///             "monster" => Ok(Self::Monster),
60	/// #            _ => panic!("Oh no")
61	///         }
62	///     }
63	/// }
64	///
65	/// let data_from_ldtk: EntityInstance = EntityInstance {
66	///     identifier: "player".to_string(),
67	///     // ...other properties
68	/// #         smart_color: "".to_string(),
69	/// #         grid: vec![],
70	/// #         pivot: vec![],
71	/// #         tags: vec![],
72	/// #         tile: None,
73	/// #         world_x: None,
74	/// #         world_y: None,
75	/// #         def_uid: 0,
76	/// #         field_instances: vec![],
77	/// #         height: 0,
78	/// #         iid: "".to_string(),
79	/// #         px: vec![],
80	/// #         width: 0,
81	/// };
82	/// #
83	/// # let process_ldtk_data = || -> InstanceRef<'_> {
84	/// #     InstanceRef {
85	/// #         entity: &data_from_ldtk,
86	/// #     }
87	/// # };
88	///
89	/// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
90	/// assert_eq!(my_entity_type.try_get_typed_id(), Ok(MyEntityType::Player));
91	/// ```
92	pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> {
93		T::from_str(self.get_type().as_str())
94	}
95
96	/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
97	/// if there is no property with the given name
98	pub fn property(&self, name: impl ToString) -> serde_json::Value {
99		self[name].clone()
100	}
101
102	/// Get a reference to the inner instance of this instance ref
103	pub fn instance_ref(&self) -> &EntityInstance {
104		self.entity
105	}
106}
107
108impl<T: ToString> Index<T> for InstanceRef<'_> {
109	type Output = serde_json::Value;
110
111	fn index(&self, index: T) -> &Self::Output {
112		let name = index.to_string();
113		for field in &self.entity.field_instances {
114			if field.identifier == name {
115				return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
116			}
117		}
118
119		&serde_json::Value::Null
120	}
121}
122
123#[derive(Copy, Clone, Debug)]
124pub struct CameraBounds {
125	pub left: f32,
126	pub top: f32,
127	pub bottom: f32,
128	pub right: f32,
129}
130
131impl CameraBounds {
132	pub fn get_min_x(&self, camera_width: f32) -> f32 {
133		self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
134	}
135	pub fn get_max_x(&self, camera_width: f32) -> f32 {
136		self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
137	}
138	pub fn get_min_y(&self, camera_height: f32) -> f32 {
139		self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
140	}
141	pub fn get_max_y(&self, camera_height: f32) -> f32 {
142		self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
143	}
144}
145
146impl MapQuery {
147	// --- We put our logic in static accessors because we might source a level other
148	// --- than the currently active one. 'active' methods are a convenience to
149	// --- call the static accessors on whatever the current level is
150
151	/// Perform an action on each layer of the given LDTK level
152	pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
153		for layer in level.layers() {
154			cb(layer);
155		}
156	}
157
158	/// Retrieve an iterator over every layer in the given level, regardless of type
159	pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
160		level.layers()
161	}
162
163	/// Retrieve a reference to every entity stored in the given level, regardless of which layer it is found on
164	pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> {
165		level
166			.layers()
167			.flat_map(|layer| layer.as_ref().entity_instances.iter())
168			.collect()
169	}
170
171	/// Retrieve an enhanced wrapper to every entity stored in the given level, regardless of which layer it is found on
172	pub fn get_instance_refs_of(level: &'_ LdtkLevel) -> Vec<InstanceRef<'_>> {
173		level
174			.layers()
175			.flat_map(|layer| {
176				layer
177					.as_ref()
178					.entity_instances
179					.iter()
180					.map(|inst| InstanceRef { entity: inst })
181			})
182			.collect()
183	}
184
185	/// Retrieve a reference to every entity stored in the given level that matches the specified type name.
186	/// This must exactly match the name shown in the LDTK entity list
187	pub fn get_filtered_entities_of(
188		level: &LdtkLevel,
189		entity_type: impl ToString,
190	) -> Vec<&EntityInstance> {
191		let e_type = entity_type.to_string();
192		level
193			.layers()
194			.flat_map(|layer| layer.as_ref().entity_instances.iter())
195			.filter(|inst| inst.identifier == e_type)
196			.collect()
197	}
198
199	/// Retrieve an enhanced wrapper to every entity stored in the given level that matches the specified type name.
200	/// This must exactly match the name shown in the LDTK entity list
201	pub fn get_filtered_instance_refs_of(
202		level: &'_ LdtkLevel,
203		entity_type: impl ToString,
204	) -> Vec<InstanceRef<'_>> {
205		let e_type = entity_type.to_string();
206		level
207			.layers()
208			.flat_map(|layer| {
209				layer
210					.as_ref()
211					.entity_instances
212					.iter()
213					.map(|inst| InstanceRef { entity: inst })
214			})
215			.filter(|inst| inst.entity.identifier == e_type)
216			.collect()
217	}
218
219	/// Retrieve an owned copy of all entity data in the given level
220	pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
221		level
222			.layers()
223			.flat_map(|layer| layer.as_ref().entity_instances.iter().cloned())
224			.collect()
225	}
226
227	/// Use the size of the level to create a zero-based rectangle indicating the boundaries that a camera should
228	///stay within to avoid showing any out-of-level space
229	pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
230		let level = level.level_ref();
231		CameraBounds {
232			left: 0.0,
233			top: level.px_hei as f32,
234			bottom: 0.0,
235			right: level.px_wid as f32,
236		}
237	}
238}