1mod quicktype;
2
3use comfy_core::*;
4use grids::Grid;
5use notify::{Config, RecommendedWatcher, Watcher};
6
7pub use quicktype::*;
8pub use serde_json;
9
10pub fn parse_ldtk_map(
11 map: &str,
12) -> Result<quicktype::LdtkJson, serde_json::Error> {
13 serde_json::from_str(map)
14}
15
16pub struct LdtkWorldMap {
17 #[cfg(not(feature = "ci-release"))]
18 pub watcher: RecommendedWatcher,
19 pub json: LdtkJson,
20 #[cfg(not(feature = "ci-release"))]
21 pub recv: std::sync::mpsc::Receiver<Result<notify::Event, notify::Error>>,
22 pub path: String,
23}
24
25impl LdtkWorldMap {
26 pub fn new(json: LdtkJson, path: &str) -> Self {
27 #[cfg(not(feature = "ci-release"))]
28 let (send, recv) = std::sync::mpsc::channel();
29
30 #[cfg(not(feature = "ci-release"))]
31 let mut watcher =
32 RecommendedWatcher::new(send, Config::default()).unwrap();
33 #[cfg(not(feature = "ci-release"))]
34 watcher
35 .watch(Path::new(path), notify::RecursiveMode::NonRecursive)
36 .unwrap();
37
38 Self {
39 json,
40 #[cfg(not(feature = "ci-release"))]
41 watcher,
42 #[cfg(not(feature = "ci-release"))]
43 recv,
44 path: path.to_string(),
45 }
46 }
47
48 #[cfg(feature = "ci-release")]
49 pub fn maybe_reload(&mut self) {}
50
51 #[cfg(not(feature = "ci-release"))]
52 pub fn maybe_reload(&mut self) {
53 let mut reload_level = false;
54
55 while let Ok(_event) = self.recv.try_recv() {
56 reload_level = true;
57 }
58
59 if reload_level {
60 match parse_ldtk_map(&std::fs::read_to_string(&self.path).unwrap())
61 {
62 Ok(json) => {
64 println!("Reloaded map");
65 self.json = json;
66 }
67 Err(err) => {
68 println!("Error parsing map {err:?}");
69 }
70 }
71 }
72 }
73}
74
75pub trait LdtkLevelExtensions {
76 fn id_position(&self, identifier: &str) -> Option<Vec2>;
77}
78
79impl LdtkLevelExtensions for Level {
80 fn id_position(&self, identifier: &str) -> Option<Vec2> {
81 let mut result = None;
82
83 for layer in self.layer_instances.as_ref()?.iter() {
84 layer.entity_instances.iter().for_each(|entity| {
85 if entity.identifier == identifier {
86 let pos = vec2(
87 entity.px[0] as f32,
88 self.px_hei as f32 -
89 entity.px[1] as f32 -
90 layer.grid_size as f32,
91 );
92 result = Some(pos / layer.grid_size as f32);
93 }
94 });
95 }
96
97 result
98 }
99}
100
101pub trait LdtkLayerExtensions {
102 fn grid_to_world(&self, x: i32, y: i32) -> Vec2;
103 fn px_to_world(&self, position: Vec2) -> Vec2;
104}
105
106impl LdtkLayerExtensions for LayerInstance {
107 fn grid_to_world(&self, x: i32, y: i32) -> Vec2 {
108 vec2(x as f32, self.c_hei as f32 - y as f32 - 1.0)
109 }
110
111 fn px_to_world(&self, position: Vec2) -> Vec2 {
112 let grid = self.grid_size as f32;
113
114 vec2(position.x / grid, self.c_hei as f32 - position.y / grid - 1.0)
115 }
116}
117
118pub trait LdtkTileExtensions {
119 fn to_world(&self, layer: &LayerInstance) -> Vec2;
120}
121
122impl LdtkTileExtensions for TileInstance {
123 fn to_world(&self, layer: &LayerInstance) -> Vec2 {
124 layer.px_to_world(vec2(self.px[0] as f32, self.px[1] as f32))
125 }
126}
127
128
129pub trait LdtkEntityExtensions {
130 fn world_pos(&self, layer_c_hei: i64, layer_grid_size: i64) -> Vec2;
131 fn world_size(&self, layer_grid_size: i64) -> Vec2;
132
133 fn bool_field(&self, name: &str) -> Option<bool>;
134 fn str_field(&self, name: &str) -> Option<&str>;
135 fn str_array_field(&self, name: &str) -> Option<Vec<String>>;
136 fn entity_array_field(&self, name: &str) -> Option<Vec<String>>;
137}
138
139impl LdtkEntityExtensions for EntityInstance {
140 fn world_pos(&self, layer_c_hei: i64, layer_grid_size: i64) -> Vec2 {
141 let grid_size = layer_grid_size as f32;
142 let entity_size = self.world_size(layer_grid_size);
143
144 vec2(
145 self.px[0] as f32,
146 (layer_c_hei as f32 - 1.0) * grid_size - self.px[1] as f32,
147 ) / grid_size +
148 vec2(entity_size.x, -entity_size.y) / 2.0
149 }
150
151 fn world_size(&self, layer_grid_size: i64) -> Vec2 {
152 let grid_size = layer_grid_size as f32;
153 vec2(self.width as f32, self.height as f32) / grid_size
154 }
155
156 fn bool_field(&self, name: &str) -> Option<bool> {
157 self.field_instances
158 .iter()
159 .find(|x| x.identifier == name)
160 .and_then(|x| x.value.as_ref())
161 .and_then(|x| x.as_bool())
162 }
163
164 fn str_field(&self, name: &str) -> Option<&str> {
165 self.field_instances
166 .iter()
167 .find(|x| x.identifier == name)
168 .and_then(|x| x.value.as_ref())
169 .and_then(|x| x.as_str())
170 }
171
172 fn str_array_field(&self, name: &str) -> Option<Vec<String>> {
173 let field =
174 self.field_instances.iter().find(|x| x.identifier == name)?;
175 let array = field.value.as_ref()?.as_array()?;
176 let strings = array.iter().filter_map(|x| x.as_str());
177 Some(strings.map(|x| x.to_string()).collect_vec())
178 }
179
180 fn entity_array_field(&self, name: &str) -> Option<Vec<String>> {
181 let field =
182 self.field_instances.iter().find(|x| x.identifier == name)?;
183 let array = field.value.as_ref()?.as_array()?;
184 let strings = array
185 .iter()
186 .filter_map(|x| x.get("entityIid"))
187 .filter_map(|x| x.as_str());
188 Some(strings.map(|x| x.to_string()).collect_vec())
189 }
190}
191
192pub fn grid_from_csv(layer: &LayerInstance) -> Grid<i32> {
193 let width = layer.c_wid as i32;
194 let height = layer.c_hei as i32;
195
196 Grid::filled_with(width, height, |x, y| {
197 layer.int_grid_csv[(x + y * width) as usize] as i32
198 })
199}