1use std::fs::File;
2use std::io::Read;
3
4use serde::Deserialize;
5
6use crate::settings::TILE_SIZE;
7use crate::tilemap::{ImageSource, Tilemap};
8use crate::utils::remove_whitespace;
9use crate::SharedTilemap;
10
11#[derive(Debug, Deserialize)]
12struct Tileset {
13 firstgid: u32,
14 columns: Option<u32>,
15}
16
17#[derive(Debug, Deserialize)]
18struct LayerData {
19 #[serde(rename = "encoding")]
20 encoding: String,
21 #[serde(rename = "$value")]
22 tiles: String,
23}
24
25#[derive(Debug, Deserialize)]
26struct Layer {
27 width: u32,
28 height: u32,
29 data: LayerData,
30}
31
32#[derive(Debug, Deserialize)]
33struct TiledMapFile {
34 tilewidth: u32,
35 tileheight: u32,
36 #[serde(rename = "tileset", default)]
37 tilesets: Vec<Tileset>,
38 #[serde(rename = "layer", default)]
39 layers: Vec<Layer>,
40}
41
42impl Tilemap {
43 pub fn from_tmx(filename: &str, layer_index: u32) -> SharedTilemap {
44 macro_rules! assert_or_break {
45 ($condition:expr, $fmt:expr $(,$arg:tt)*) => {
46 if !$condition {
47 println!($fmt, $($arg)*);
48 break;
49 }
50 };
51 }
52
53 #[allow(clippy::never_loop)]
54 loop {
55 let file = File::open(filename);
56 assert_or_break!(file.is_ok(), "Failed to open file '{filename}'");
57 let mut file = file.unwrap();
58
59 let mut tmx_text = String::new();
60 assert_or_break!(
61 file.read_to_string(&mut tmx_text).is_ok(),
62 "Failed to read TMX file"
63 );
64
65 let tmx = serde_xml_rs::from_str(&tmx_text);
66 assert_or_break!(tmx.is_ok(), "Failed to parse TMX file");
67 let tmx: TiledMapFile = tmx.unwrap();
68
69 assert_or_break!(
70 tmx.tilewidth == TILE_SIZE && tmx.tileheight == TILE_SIZE,
71 "TMX file's tile size is not {TILE_SIZE}x{TILE_SIZE}"
72 );
73
74 assert_or_break!(!tmx.tilesets.is_empty(), "Tileset not found in TMX file");
75 let tileset = &tmx.tilesets[0];
76 assert_or_break!(
77 tileset.columns.is_some(),
78 "Tileset is not embedded in TMX file"
79 );
80 let tileset_columns = tileset.columns.unwrap();
81
82 assert_or_break!(
83 layer_index < tmx.layers.len() as u32,
84 "Layer {layer_index} not found in TMX file"
85 );
86 let layer = &tmx.layers[layer_index as usize];
87 assert_or_break!(
88 layer.data.encoding == "csv",
89 "TMX file's encoding is not CSV"
90 );
91
92 let layer_data: Vec<u32> = remove_whitespace(&layer.data.tiles)
93 .split(',')
94 .map(|s| s.parse::<u32>().unwrap())
95 .collect();
96
97 let tilemap = Self::new(layer.width, layer.height, ImageSource::Index(0));
98 {
99 let mut tilemap = tilemap.lock();
100 for (i, tile_id) in layer_data.iter().enumerate() {
101 let x = i % layer.width as usize;
102 let y = i / layer.width as usize;
103 let tile_id = if *tile_id > tileset.firstgid {
104 tile_id - tileset.firstgid
105 } else {
106 0
107 };
108 let tile_x = (tile_id % tileset_columns) as u8;
109 let tile_y = (tile_id / tileset_columns) as u8;
110 tilemap.canvas.write_data(x, y, (tile_x, tile_y));
111 }
112 }
113
114 return tilemap;
115 }
116
117 Self::new(1, 1, ImageSource::Index(0))
119 }
120}