From dfb52c4f960c71af307046c2b7e40abdef57aa20 Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Mon, 6 Feb 2023 13:17:30 +0000 Subject: [PATCH] Add new types for map data --- src/types.rs | 415 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.rs | 2 +- 2 files changed, 410 insertions(+), 7 deletions(-) diff --git a/src/types.rs b/src/types.rs index 676ad47..c24dc7c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,14 +1,16 @@ +use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use std::ops::{Deref, DerefMut}; use std::path::Path; -use bevy::math::{IVec2, UVec2}; -use bevy::utils::HashMap; -use ldtk_rust::{LayerInstance, Level, TileInstance}; +use bevy::math::{IVec2, Rect, UVec2, Vec2}; +use ldtk_rust::{EntityInstance, FieldInstance, LayerInstance, Level, TileInstance}; use num_traits::AsPrimitive; use quadtree_rs::area::{Area, AreaBuilder}; use quadtree_rs::point::Point; use quadtree_rs::Quadtree; -use serde_json::Value; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Number, Value}; use crate::utils::{Indexer, SerdeClone}; use crate::{get_ldtk_tile_scale, px_to_grid, MapQuery}; @@ -21,7 +23,7 @@ pub struct TileRef<'a> { } #[repr(C)] -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug)] +#[derive(Serialize, Deserialize, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug)] pub enum TileFlip { #[default] None = 0, @@ -74,7 +76,7 @@ impl<'a> Debug for TileRef<'a> { } } -#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Hash, Debug)] +#[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 @@ -316,3 +318,404 @@ impl AsRef<LayerInstance> for LdtkLayer { &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(format!("{}", value)), + 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 { + match self.0.get(&name.to_string()) { + Some(Value::Null) => true, + _ => false, + } + } + + pub fn is_null_or_undefined(&self, name: impl ToString) -> bool { + match self.0.get(&name.to_string()) { + None | Some(Value::Null) => true, + _ => false, + } + } + + 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)) { + match &self.layer_data { + LayerType::GidTiles(map) => { + 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)) { + match &mut self.layer_data { + LayerType::GidTiles(map) => { + 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() +// } +// } +// } diff --git a/src/utils.rs b/src/utils.rs index 76e3674..d33f47f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,7 +36,7 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) { #[derive(Component)] pub struct WorldLinked; -#[derive(Default, Resource, Clone)] +#[derive(Default, Resource, Clone, Debug)] pub struct ActiveLevel { pub map: String, pub dirty: bool, -- GitLab