use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::ops::{Deref, DerefMut}; use std::path::Path; use bevy::math::{IVec2, Rect, UVec2, Vec2}; use bevy::prelude::Event; use num_traits::AsPrimitive; use quadtree_rs::area::AreaBuilder; use quadtree_rs::point::Point; use quadtree_rs::Quadtree; use serde::{Deserialize, Serialize}; use serde_json::{Map, Number, Value}; use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, TileInstance}; use crate::system::Indexer; use crate::{get_ldtk_tile_scale, px_to_grid, MapQuery}; #[derive(Default, Clone, Debug, Ord, PartialOrd, PartialEq, Eq, Event)] pub struct LevelDataUpdated(pub String); pub struct TileRef<'a> { pub tile: &'a TileInstance, } #[repr(C)] #[derive(Serialize, Deserialize, 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() } } #[derive(Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Hash, Debug)] pub struct SpatialIndex(i64, i64); impl<A, B> From<(A, B)> for SpatialIndex where A: AsPrimitive<i64>, B: AsPrimitive<i64>, { fn from(value: (A, B)) -> Self { Self(value.0.as_(), value.1.as_()) } } impl From<UVec2> for SpatialIndex { fn from(value: UVec2) -> Self { Self(value.x as i64, value.y as i64) } } impl From<IVec2> for SpatialIndex { fn from(value: IVec2) -> Self { Self(value.x as i64, value.y as i64) } } pub type ColliderQuadtree = Quadtree<i64, LocatedEntity>; pub struct LdtkLevel { level: Level, properties: HashMap<String, Value>, processed_layers: Vec<LdtkLayer>, colliders: ColliderQuadtree, } impl LdtkLevel { pub fn width(&self) -> f32 { self.level.px_wid as f32 } pub fn width_i(&self) -> i64 { self.level.px_wid } pub fn height(&self) -> f32 { self.level.px_hei as f32 } pub fn height_i(&self) -> i64 { self.level.px_hei } pub fn level_ref(&self) -> &Level { &self.level } pub fn level_ref_mut(&mut self) -> &mut Level { &mut self.level } pub fn colliders_ref(&self) -> &ColliderQuadtree { &self.colliders } pub fn colliders_ref_mut(&mut self) -> &mut ColliderQuadtree { &mut self.colliders } pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LdtkLayer> { self.processed_layers.iter() } pub fn layers_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut LdtkLayer> { self.processed_layers.iter_mut() } pub fn properties_ref(&self) -> &HashMap<String, Value> { &self.properties } pub fn properties_mut(&mut self) -> &mut HashMap<String, Value> { &mut self.properties } pub fn property(&self, name: impl ToString) -> Option<&Value> { self.properties.get(&name.to_string()) } pub fn property_or_null(&self, name: impl ToString) -> &Value { self.property(name).unwrap_or(&Value::Null) } pub fn get_indexer(&self) -> Indexer { Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei)) } } #[derive(Debug, Clone, Default)] pub struct LocatedEntity { pub position: IVec2, pub size: IVec2, pub properties: HashMap<String, Value>, pub collides: bool, } impl From<Level> for LdtkLevel { fn from(mut value: Level) -> Self { let layers = value.layer_instances.take(); let mut properties = HashMap::with_capacity(value.field_instances.len()); let fields = std::mem::take(&mut value.field_instances); for field in fields { properties.insert(field.identifier, field.value.unwrap_or(Value::Null)); } let _level_width = value.px_wid; let level_height = value.px_hei; let mut level = Self { colliders: Quadtree::new(0), processed_layers: layers .unwrap_or_default() .into_iter() .rev() .enumerate() .map(|(idx, inst)| LdtkLayer::new(idx, inst)) .collect(), level: value, properties, }; let mut collider_quads = Quadtree::<i64, LocatedEntity>::new(32); for entity in MapQuery::get_entities_of(&level) { if entity.tags.contains(&String::from("Collider")) { let mut properties = HashMap::with_capacity(entity.field_instances.len()); for field in entity.field_instances.iter() { properties.insert( field.identifier.clone(), field.value.as_ref().unwrap_or(&Value::Null).clone(), ); } let _width = entity.width; let height = entity.height; let _left_x = entity.px[0]; let _bottom_y = level_height - (entity.px[1] + height); let size = IVec2::new(entity.width as i32, entity.height as i32); let position = IVec2::new(level.level.px_wid as i32, level.level.px_hei as i32) - (IVec2::new(entity.px[0] as i32, entity.px[1] as i32) + size); let entity = LocatedEntity { position, size, properties, collides: true, }; if let Ok(point) = AreaBuilder::default() .anchor(Point::from((position.x as i64, position.y as i64))) .dimensions((size.x as i64, size.y as i64)) .build() { collider_quads.insert(point, entity); } } } level.colliders = collider_quads; level } } pub struct LdtkLayer { pub level: usize, layer: LayerInstance, position_lookup: HashMap<SpatialIndex, TileInstance>, indexer: Indexer, } impl LdtkLayer { pub fn new(index: usize, layer: LayerInstance) -> Self { let tile_list = layer.grid_tiles.iter().chain(&layer.auto_layer_tiles); let mut position_lookup = HashMap::with_capacity(layer.grid_tiles.len() + layer.auto_layer_tiles.len()); let scale = get_ldtk_tile_scale() as i64; let indexer = Indexer::new(layer.c_wid, layer.c_hei); for tile in tile_list { let x = tile.px[0] / scale; let y = tile.px[1] / scale; position_lookup.insert((x, y).into(), tile.clone()); } Self { level: index, indexer, layer, position_lookup, } } pub fn name(&self) -> &String { &self.layer.identifier } pub fn indexer(&self) -> Indexer { self.indexer } 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.position_lookup.iter().for_each(|(pos, tile)| { cb(pos.0, pos.1, TileRef::new(tile)); }); } pub fn get_z_delta(&self) -> f32 { (self.level as f32) / 100.0 } pub fn get_tile(&self, pos: impl Into<SpatialIndex>) -> Option<TileRef> { self.position_lookup.get(&pos.into()).map(TileRef::new) } pub fn get_tile_at( &self, a: impl AsPrimitive<i64>, b: impl AsPrimitive<i64>, ) -> Option<TileRef> { self.position_lookup .get(&SpatialIndex(a.as_(), b.as_())) .map(TileRef::new) } /// 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) }) } } impl AsRef<LayerInstance> for LdtkLayer { fn as_ref(&self) -> &LayerInstance { &self.layer } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct WorldTile { gid: usize, rotation: TileFlip, } impl From<&TileInstance> for WorldTile { fn from(value: &TileInstance) -> Self { Self { gid: value.t.max(0) as usize, rotation: match value.f { 1 => TileFlip::Horizontal, 2 => TileFlip::Vertical, 3 => TileFlip::Both, _ => TileFlip::None, }, } } } #[derive(Serialize, Deserialize, Clone, Copy, Debug)] pub enum EntityPosition { Point { position: IVec2 }, Zone { position: IVec2, size: IVec2 }, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct WorldEntity { entity_type: String, position: Rect, properties: Properties, tags: Vec<String>, colour: String, } impl From<&EntityInstance> for WorldEntity { fn from(value: &EntityInstance) -> Self { let x = value.px[0]; let y = value.px[1]; Self { entity_type: value.identifier.clone(), position: Rect::from_corners( Vec2::new(x as f32, y as f32), Vec2::new((x + value.width) as f32, (y + value.height) as f32), ), properties: Properties::from(&value.field_instances), tags: value.tags.clone(), colour: value.smart_color.clone(), } } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct Properties(HashMap<String, Value>); impl Deref for Properties { type Target = HashMap<String, Value>; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Properties { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl From<HashMap<String, Value>> for Properties { fn from(value: HashMap<String, Value>) -> Self { Self(value) } } impl From<Vec<FieldInstance>> for Properties { fn from(value: Vec<FieldInstance>) -> Self { (&value).into() } } impl From<&Vec<FieldInstance>> for Properties { fn from(value: &Vec<FieldInstance>) -> Self { value .iter() .map(|field| { ( field.identifier.clone(), field.value.clone().unwrap_or(Value::Null), ) }) .collect::<HashMap<String, Value>>() .into() } } fn convert_map_types(map: &Map<String, Value>) -> HashMap<String, Value> { map.iter() .map(|(name, value)| (name.clone(), value.clone())) .collect() } impl Properties { pub fn as_string(&self, name: impl ToString) -> Option<String> { self.get(&name.to_string()).and_then(|value| match value { Value::String(value) => Some(value.to_string()), Value::Bool(value) => Some(format!("{}", value)), Value::Number(value) => Some(format!("{}", value)), _ => None, }) } pub fn as_number_cast<T>(&self, name: impl ToString) -> Option<T> where T: 'static + Copy, f64: AsPrimitive<T>, { self.get(&name.to_string()).and_then(|value| match value { Value::Number(number) => { let num = number.as_f64(); num.map(|val| val.as_()) } _ => None, }) } pub fn as_bool(&self, name: impl ToString) -> Option<bool> { self.get(&name.to_string()).and_then(|value| match value { Value::Bool(value) => Some(*value), _ => None, }) } pub fn as_vec(&self, name: impl ToString) -> Option<&Vec<Value>> { self.get(&name.to_string()).and_then(|value| match value { Value::Array(value) => Some(value), _ => None, }) } pub fn as_vec_owned(&self, name: impl ToString) -> Option<Vec<Value>> { self.get(&name.to_string()).and_then(|value| match value { Value::Array(value) => Some(value.clone()), _ => None, }) } pub fn as_map(&self, name: impl ToString) -> Option<&Map<String, Value>> { self.get(&name.to_string()).and_then(|value| match value { Value::Object(value) => Some(value), _ => None, }) } pub fn as_map_owned(&self, name: impl ToString) -> Option<Map<String, Value>> { self.get(&name.to_string()).and_then(|value| match value { Value::Object(value) => Some(value.clone()), _ => None, }) } pub fn matches(&self, name: impl ToString, value: Value) -> bool { match (self.0.get(&name.to_string()), value) { (Some(Value::String(a)), Value::String(ref b)) => a == b, (Some(Value::Number(a)), Value::Number(ref b)) => a == b, (Some(Value::Bool(a)), Value::Bool(ref b)) => a == b, (Some(Value::Array(a)), Value::Array(ref b)) => a == b, (Some(Value::Object(a)), Value::Object(ref b)) => a == b, (Some(Value::Null), Value::Null) => true, _ => false, } } pub fn is_null(&self, name: impl ToString) -> bool { matches!(self.0.get(&name.to_string()), Some(Value::Null)) } pub fn is_null_or_undefined(&self, name: impl ToString) -> bool { matches!(self.0.get(&name.to_string()), None | Some(Value::Null)) } pub fn is_null_or_falsy(&self, name: impl ToString) -> bool { match self.0.get(&name.to_string()) { None | Some(Value::Null) => true, Some(Value::Bool(value)) => !*value, Some(Value::String(value)) => value.is_empty(), Some(Value::Number(num)) => num == &Number::from(0), Some(Value::Array(value)) => value.is_empty(), Some(Value::Object(value)) => value.is_empty(), } } } #[derive(Serialize, Deserialize, Clone, Debug)] pub enum LayerType { Entities(Vec<WorldEntity>), GidTiles(HashMap<SpatialIndex, WorldTile>), IntTiles(Vec<usize>), } fn infer_tileset_name(layer: &LayerInstance) -> Option<String> { layer.tileset_rel_path.as_ref().and_then(|path| { Path::new(path) .file_stem() .and_then(|stem| stem.to_str()) .map(String::from) }) } impl From<&LayerInstance> for LayerType { fn from(value: &LayerInstance) -> Self { match value.layer_instance_type.as_str() { "IntGrid" => LayerType::IntTiles( value .int_grid_csv .iter() .map(|num| (*num).max(0) as usize) .collect(), ), "Entities" => LayerType::Entities( value .entity_instances .iter() .map(|entity| entity.into()) .collect(), ), "Tiles" => LayerType::GidTiles( value .grid_tiles .iter() .map(|tile| (SpatialIndex(tile.px[0], tile.px[1]), tile.into())) .collect(), ), "AutoLayer" => LayerType::GidTiles( value .auto_layer_tiles .iter() .map(|tile| (SpatialIndex(tile.px[0], tile.px[1]), tile.into())) .collect(), ), _ => { panic!("Invalid layer type {}", value.layer_instance_type.as_str()); } } } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct WorldLayer { id: String, depth: usize, world_width: i64, world_height: i64, tile_width: i64, tile_height: i64, layer_data: LayerType, tileset_name: Option<String>, } impl WorldLayer { pub fn from_layer(depth: usize, layer: &LayerInstance) -> Self { let tile_size = get_ldtk_tile_scale() as i64; Self { depth, id: layer.identifier.clone(), tileset_name: infer_tileset_name(layer), world_width: layer.c_wid * tile_size, world_height: layer.c_hei * tile_size, tile_width: layer.c_wid, tile_height: layer.c_hei, layer_data: layer.into(), } } pub fn has_tiles(&self) -> bool { match &self.layer_data { LayerType::GidTiles(map) => !map.is_empty(), _ => false, } } pub fn for_each_tile(&self, mut cb: impl FnMut(i64, i64, &WorldTile)) { if let LayerType::GidTiles(map) = &self.layer_data { map.iter().for_each(|(pos, tile)| { cb(pos.0, pos.1, tile); }); } } pub fn for_each_tile_mut(&mut self, mut cb: impl FnMut(i64, i64, &mut WorldTile)) { if let LayerType::GidTiles(map) = &mut self.layer_data { map.iter_mut().for_each(|(pos, tile)| { cb(pos.0, pos.1, tile); }); } } pub fn get_z_delta(&self) -> f32 { (self.depth as f32) / 100.0 } pub fn get_tile( &self, x: impl AsPrimitive<i32>, y: impl AsPrimitive<i32>, ) -> Option<&WorldTile> { match &self.layer_data { LayerType::GidTiles(map) => map.get(&IVec2::from((x.as_(), y.as_())).into()), _ => None, } } pub fn get_tile_mut( &mut self, x: impl AsPrimitive<i32>, y: impl AsPrimitive<i32>, ) -> Option<&mut WorldTile> { match &mut self.layer_data { LayerType::GidTiles(map) => map.get_mut(&IVec2::from((x.as_(), y.as_())).into()), _ => None, } } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct WorldData { layers: Vec<WorldLayer>, properties: Properties, meta: Properties, } impl From<&Level> for WorldData { fn from(value: &Level) -> Self { let mut properties: Properties = (&value.field_instances).into(); let meta: Properties = properties .as_map("meta") .map(convert_map_types) .unwrap_or_default() .into(); properties.remove("meta"); Self { properties, meta, layers: value .layer_instances .as_ref() .map(|insts| { insts .iter() .enumerate() .map(|(idx, layer)| WorldLayer::from_layer(idx, layer)) .collect() }) .unwrap_or_default(), } } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct LayerSet(pub Vec<WorldLayer>); trait AsWorldLayer { fn as_world_layer(&self, depth: usize) -> WorldLayer; } // impl<T, V> From<V> for LayerSet // where // T: AsWorldLayer, // V: Iterator<Item = T>, // { // fn from(value: V) -> Self { // Self(value.enumerate().map(T::as_world_layer).collect()) // } // } // impl<T> From<T> for WorldData // where // T: Into<LayerSet>, // { // fn from(value: T) -> Self { // let layers = value.into(); // WorldData { // layers: layers.0, // ..Default::default() // } // } // }