Skip to content
Snippets Groups Projects
map_query_neutral.rs 7.65 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> {
    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,
        }
    }
}