Skip to content
Snippets Groups Projects
map_query_neutral.rs 6.7 KiB
Newer Older
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> {
Louis's avatar
Louis committed
	pub entity: &'a EntityInstance,
}

impl<'a> InstanceRef<'a> {
Louis's avatar
Louis committed
	/// 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
	}
Louis's avatar
Louis committed
impl<T: ToString> Index<T> for InstanceRef<'_> {
	type Output = serde_json::Value;
Louis's avatar
Louis committed
	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);
			}
		}
Louis's avatar
Louis committed
		&serde_json::Value::Null
	}
}

#[derive(Copy, Clone, Debug)]
pub struct CameraBounds {
Louis's avatar
Louis committed
	pub left: f32,
	pub top: f32,
	pub bottom: f32,
	pub right: f32,
}

impl CameraBounds {
Louis's avatar
Louis committed
	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)
	}
Louis's avatar
Louis committed
	// --- 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,
		}
	}