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