Newer
Older
use std::fmt::{Debug, Formatter};
use std::path::Path;
use bevy::math::{IVec2, UVec2};
use bevy::utils::HashMap;
use ldtk_rust::{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 crate::utils::{Indexer, SerdeClone};
use crate::{get_ldtk_tile_scale, px_to_grid, MapQuery};
#[derive(Default, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
pub struct LevelDataUpdated(pub String);
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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()
}
}
#[derive(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,
processed_layers: Vec<LdtkLayer>,
}
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_else(|| &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_else(|| 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()
.enumerate()
.map(|(idx, inst)| LdtkLayer::new(idx, inst))
.collect(),
level: value,
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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_else(|| &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,
};
match AreaBuilder::default()
.anchor(Point::from((position.x as i64, position.y as i64)))
.dimensions((size.x as i64, size.y as i64))
.build()
{
Ok(point) => {
collider_quads.insert(point, entity);
}
Err(_) => {}
}
}
}
}
pub struct LdtkLayer {
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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.serde_clone());
}
Self {
level: index,
indexer,
layer,
position_lookup,
}
}
pub fn name(&self) -> &String {
&self.layer.identifier
}
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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
}
}