use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; use std::path::Path; use std::str::FromStr; use bevy::ecs::system::SystemParam; use bevy::prelude::*; use ldtk_rust::{EntityInstance, LayerInstance, Level, TileInstance}; use num_traits::AsPrimitive; // use crate::assets::level_index::LevelIndex; use crate::assets::{LdtkProject, LevelIndex}; use crate::utils::{ActiveLevel, Indexer, SerdeClone}; use crate::{get_ldtk_tile_scale, px_to_grid}; #[derive(SystemParam)] pub struct MapQuery<'w, 's> { assets: Res<'w, Assets<LdtkProject>>, active: Option<Res<'w, ActiveLevel>>, index: Res<'w, LevelIndex>, #[system_param(ignore)] _e: PhantomData<&'s ()>, } pub struct TileRef<'a> { pub tile: &'a TileInstance, } #[repr(C)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug)] pub enum TileFlip { #[default] None = 0, Horizontal = 1, Vertical = 2, Both = 3, } impl TileFlip { pub fn is_horizontal(&self) -> bool { match self { Self::None | Self::Vertical => false, Self::Horizontal | Self::Both => true, } } pub fn is_vertical(&self) -> bool { match self { Self::None | Self::Horizontal => false, Self::Vertical | Self::Both => true, } } } impl<'a> TileRef<'a> { pub fn new(tile: &'a TileInstance) -> Self { TileRef { tile } } pub fn gid(&self) -> usize { (self.tile.src[0] * self.tile.src[1]).unsigned_abs() as usize } pub fn get_flip(&self) -> TileFlip { match self.tile.f { 1 => TileFlip::Horizontal, 2 => TileFlip::Vertical, 3 => TileFlip::Both, _ => TileFlip::None, } } } impl<'a> Debug for TileRef<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("TileRef") .field("gid", &self.gid()) .field("tile.t", &self.tile.t) .field("tile.d", &self.tile.d) .field("tile.px", &self.tile.px) .field("tile.src", &self.tile.src) .finish() } } pub struct InstanceRef<'a> { pub entity: &'a EntityInstance, } impl<'a> InstanceRef<'a> { pub fn get_type(&self) -> &'a String { &self.entity.identifier } pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> { T::from_str(self.get_type().as_str()) } pub fn property(&self, name: impl ToString) -> serde_json::Value { let target = name.to_string(); for field in &self.entity.field_instances { if field.identifier == target { return field .value .as_ref() .map(|v| v.clone()) .unwrap_or(serde_json::Value::Null); } } serde_json::Value::Null } } pub struct LayerRef<'a> { pub idx: usize, pub indexer: Indexer, pub layer: &'a LayerInstance, } impl<'a> LayerRef<'a> { pub fn new(idx: usize, indexer: Indexer, layer: &'a LayerInstance) -> Self { LayerRef { layer, indexer, idx, } } pub fn has_tiles(&self) -> bool { !(self.layer.auto_layer_tiles.is_empty() && self.layer.grid_tiles.is_empty()) } pub fn for_each_tile(&self, mut cb: impl FnMut(i64, i64, TileRef)) { self.layer .grid_tiles .iter() .chain(self.layer.auto_layer_tiles.iter()) .for_each(|tile: &TileInstance| { let (x, y) = match tile.px.as_slice() { &[x, y] => (px_to_grid(x), px_to_grid(y)), _ => { return; } }; cb(x, y, TileRef::new(tile)); }); } pub fn get_z_delta(&self) -> f32 { (self.idx as f32) / 100.0 } pub fn get_tile_at( &self, x: impl AsPrimitive<isize>, y: impl AsPrimitive<isize>, ) -> Option<TileRef> { let idx = self.indexer.index(x, y); match self.layer.grid_tiles.is_empty() { true => self .layer .auto_layer_tiles .get(idx) .map(|tile| TileRef::new(tile)), false => self .layer .grid_tiles .get(idx) .map(|tile| TileRef::new(tile)), } } /// Returns the inferred name of the tileset used for this layer. This is assumed to be the /// name of the tileset file, without the preceding path segments or the file extension. Case /// remains unchanged pub fn infer_tileset_name(&self) -> Option<String> { self.layer.tileset_rel_path.as_ref().and_then(|path| { Path::new(path) .file_stem() .and_then(|stem| stem.to_str()) .map(String::from) }) } } #[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<'w, 's> MapQuery<'w, 's> { // --- 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 pub fn get_indexer_for(level: &Level) -> Indexer { Indexer::new(px_to_grid(level.px_wid), px_to_grid(level.px_hei)) } pub fn for_each_layer_of(level: &Level, mut cb: impl FnMut(LayerRef)) { if let Some(layers) = level.layer_instances.as_ref() { for layer in layers.iter().rev().enumerate() { cb(LayerRef::new( layer.0, MapQuery::get_indexer_for(level), layer.1, )); } } } pub fn get_entities_of(level: &Level) -> Vec<&EntityInstance> { level .layer_instances .as_ref() .map(|layers| { layers .iter() .flat_map(|layer| layer.entity_instances.iter()) .collect() }) .unwrap_or_default() } pub fn get_instance_refs_of(level: &Level) -> Vec<InstanceRef> { level .layer_instances .as_ref() .map(|layers| { layers .iter() .flat_map(|layer| layer.entity_instances.iter()) .map(|inst| InstanceRef { entity: inst }) .collect() }) .unwrap_or_default() } pub fn get_filtered_entities_of( level: &Level, entity_type: impl ToString, ) -> Vec<&EntityInstance> { let e_type = entity_type.to_string(); match level.layer_instances.as_ref() { Some(inst) => inst .iter() .flat_map(|layer| layer.entity_instances.iter()) .filter(|inst| inst.identifier == e_type) .collect(), None => Vec::new(), } } pub fn get_owned_entities_of(level: &Level) -> Vec<EntityInstance> { level .layer_instances .as_ref() .map(|layers| { layers .iter() .flat_map(|layer| layer.entity_instances.iter().map(|inst| inst.serde_clone())) .collect() }) .unwrap_or_default() } pub fn get_camera_bounds_of(level: &Level) -> CameraBounds { CameraBounds { left: 0.0, top: level.px_hei as f32, bottom: 0.0, right: level.px_wid as f32, } } pub fn get_active_level(&self) -> Option<&Level> { self.active .as_ref() .and_then(|index| self.index.get(&index.map)) } pub fn get_entities(&self) -> Vec<&EntityInstance> { self.get_active_level() .map(|level| MapQuery::get_entities_of(level)) .unwrap_or_default() } pub fn get_camera_bounds(&self) -> Option<CameraBounds> { self.get_active_level().map(MapQuery::get_camera_bounds_of) } pub fn for_each_layer(&self, mut cb: impl FnMut(LayerRef)) { if let Some(level) = self.get_active_level() { Self::for_each_layer_of(level, cb); } } }