tile_set_parser.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. // SuperTux
  2. // Copyright (C) 2008 Matthias Braun <matze@braunis.de>
  3. // Ingo Ruhnke <grumbel@gmail.com>
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #include "supertux/tile_set_parser.hpp"
  18. #include <sstream>
  19. #include <sexp/value.hpp>
  20. #include <sexp/io.hpp>
  21. #include "supertux/autotile_parser.hpp"
  22. #include "supertux/gameconfig.hpp"
  23. #include "supertux/globals.hpp"
  24. #include "supertux/tile_set.hpp"
  25. #include "util/log.hpp"
  26. #include "util/reader_document.hpp"
  27. #include "util/reader_mapping.hpp"
  28. #include "util/file_system.hpp"
  29. #include "video/surface.hpp"
  30. TileSetParser::TileSetParser(TileSet& tileset, const std::string& filename,
  31. int32_t start, int32_t end, int32_t offset) :
  32. m_tileset(tileset),
  33. m_filename(filename),
  34. m_tiles_path(),
  35. m_start(start),
  36. m_end(end),
  37. m_offset(offset)
  38. {
  39. }
  40. void
  41. TileSetParser::parse(bool imported)
  42. {
  43. if (m_offset && m_start + m_offset < 1)
  44. {
  45. m_start = -m_offset + 1;
  46. log_warning << "The defined offset would assign non-positive IDs to tiles, tiles below " << -m_offset + 1 << " will be ignored." << std::endl;
  47. }
  48. if (m_end < 0)
  49. {
  50. log_warning << "Cannot import tiles with negative IDs." << std::endl;
  51. return;
  52. }
  53. if (m_start < 0)
  54. {
  55. log_warning << "Cannot import tiles with negative IDs. Importing will start at ID 1." << std::endl;
  56. m_start = 1;
  57. }
  58. m_tiles_path = FileSystem::dirname(m_filename);
  59. auto doc = ReaderDocument::from_file(m_filename);
  60. auto root = doc.get_root();
  61. if (root.get_name() != "supertux-tiles") {
  62. throw std::runtime_error("file is not a supertux tiles file.");
  63. }
  64. auto iter = root.get_mapping().get_iter();
  65. while (iter.next())
  66. {
  67. if (iter.get_key() == "tile")
  68. {
  69. parse_tile(iter.as_mapping());
  70. }
  71. else if (iter.get_key() == "tilegroup")
  72. {
  73. /* tilegroups are only interesting for the editor */
  74. ReaderMapping reader = iter.as_mapping();
  75. Tilegroup tilegroup;
  76. reader.get("name", tilegroup.name);
  77. reader.get("tiles", tilegroup.tiles);
  78. // Allow offsetting every tile ID, specified in the tilegroup
  79. int32_t tiles_offset = 0;
  80. reader.get("offset", tiles_offset);
  81. bool has_valid_tile = false;
  82. for (int& tile : tilegroup.tiles)
  83. {
  84. if (tile == 0) continue;
  85. if (tile < m_start || (m_end && tile > m_end))
  86. {
  87. tile = 0;
  88. }
  89. else
  90. {
  91. tile += m_offset + tiles_offset;
  92. has_valid_tile = true;
  93. }
  94. }
  95. if (has_valid_tile)
  96. {
  97. m_tileset.add_tilegroup(std::move(tilegroup));
  98. }
  99. }
  100. else if (iter.get_key() == "tiles")
  101. {
  102. parse_tiles(iter.as_mapping());
  103. }
  104. else if (iter.get_key() == "autotileset")
  105. {
  106. ReaderMapping reader = iter.as_mapping();
  107. std::string autotile_filename;
  108. if (!reader.get("source", autotile_filename))
  109. {
  110. log_warning << "No source path for autotiles in file '" << m_filename << "'" << std::endl;
  111. continue;
  112. }
  113. int32_t import_offset = 0;
  114. reader.get("offset", import_offset);
  115. AutotileParser parser(m_tileset.m_autotilesets,
  116. FileSystem::normalize(m_tiles_path + autotile_filename),
  117. m_start, m_end, import_offset + m_offset);
  118. parser.parse();
  119. }
  120. else if (iter.get_key() == "import-tileset")
  121. {
  122. ReaderMapping reader = iter.as_mapping();
  123. std::string import_filename;
  124. if (!reader.get("file", import_filename))
  125. {
  126. log_warning << "No source path for imported tileset in file '" << m_filename << "'" << std::endl;
  127. continue;
  128. }
  129. if (import_filename == m_filename)
  130. {
  131. log_warning << "Tried to recursively import tileset '" << m_filename << "'" << std::endl;
  132. continue;
  133. }
  134. int32_t import_start = 0, import_end = 0, import_offset = 0;
  135. reader.get("start", import_start);
  136. reader.get("end", import_end);
  137. reader.get("offset", import_offset);
  138. import_offset += m_offset;
  139. if (import_start + import_offset < m_start)
  140. {
  141. import_start = std::max(0, m_start - import_offset);
  142. }
  143. if (m_end && (!import_end || import_end + import_offset > m_end))
  144. {
  145. import_end = m_end - import_offset;
  146. }
  147. if (import_end < import_start)
  148. {
  149. if (!imported) log_warning << "The defined range has a negative size, no tiles will be imported." << std::endl;
  150. continue;
  151. }
  152. TileSetParser parser(m_tileset, import_filename,
  153. import_start, import_end, import_offset);
  154. parser.parse(true);
  155. }
  156. else if (iter.get_key() == "additional")
  157. {
  158. ReaderMapping reader = iter.as_mapping();
  159. auto additional_iter = reader.get_iter();
  160. while (additional_iter.next())
  161. {
  162. if (additional_iter.get_key() == "thunderstorm") // Additional attributes for thunderstorms.
  163. {
  164. auto info_mapping = additional_iter.as_mapping();
  165. // Additional attributes for changing tiles for thunderstorms.
  166. std::vector<uint32_t> tiles;
  167. if (info_mapping.get("changing-tiles", tiles))
  168. {
  169. if (tiles.size() < 2)
  170. {
  171. log_warning << "Less than 2 tile IDs given for thunderstorm changing tiles." << std::endl;
  172. continue;
  173. }
  174. if (tiles.size() % 2 != 0) tiles.pop_back(); // If the number of tiles isn't even, remove last tile.
  175. for (int i = 0; i < static_cast<int>(tiles.size()); i += 2)
  176. {
  177. if (tiles[i] < static_cast<uint32_t>(m_start) || (m_end && tiles[i] > static_cast<uint32_t>(m_end)) ||
  178. tiles[i + 1] < static_cast<uint32_t>(m_start) || (m_end && tiles[i + 1] > static_cast<uint32_t>(m_end)))
  179. continue; // Both tiles have to fit in the tile range.
  180. m_tileset.m_thunderstorm_tiles.insert({ tiles[i] + m_offset, tiles[i + 1] + m_offset });
  181. }
  182. }
  183. }
  184. else
  185. {
  186. log_warning << "Unknown symbol '" << additional_iter.get_key() << "' in \"additional\" section of tileset file" << std::endl;
  187. }
  188. }
  189. }
  190. else
  191. {
  192. log_warning << "Unknown symbol '" << iter.get_key() << "' in tileset file" << std::endl;
  193. }
  194. }
  195. /* Check for and remove any deprecated tiles from tilegroups */
  196. m_tileset.remove_deprecated_tiles();
  197. /* only create the unassigned tilegroup from the parent strf */
  198. if (g_config->developer_mode && !imported)
  199. {
  200. m_tileset.add_unassigned_tilegroup();
  201. }
  202. }
  203. void
  204. TileSetParser::parse_tile(const ReaderMapping& reader)
  205. {
  206. uint32_t id;
  207. if (!reader.get("id", id))
  208. {
  209. throw std::runtime_error("Missing tile-id.");
  210. }
  211. if (id < static_cast<uint32_t>(m_start) || (m_end && id > static_cast<uint32_t>(m_end))) return;
  212. id += m_offset;
  213. uint32_t attributes = 0;
  214. bool value = false;
  215. if (reader.get("solid", value) && value)
  216. attributes |= Tile::SOLID;
  217. if (reader.get("unisolid", value) && value)
  218. attributes |= Tile::UNISOLID | Tile::SOLID;
  219. if (reader.get("brick", value) && value)
  220. attributes |= Tile::BRICK;
  221. if (reader.get("ice", value) && value)
  222. attributes |= Tile::ICE;
  223. if (reader.get("water", value) && value)
  224. attributes |= Tile::WATER;
  225. if (reader.get("hurts", value) && value)
  226. attributes |= Tile::HURTS;
  227. if (reader.get("fire", value) && value)
  228. attributes |= Tile::FIRE;
  229. if (reader.get("walljump", value) && value)
  230. attributes |= Tile::WALLJUMP;
  231. if (reader.get("fullbox", value) && value)
  232. attributes |= Tile::FULLBOX;
  233. if (reader.get("coin", value) && value)
  234. attributes |= Tile::COIN;
  235. if (reader.get("goal", value) && value)
  236. attributes |= Tile::GOAL;
  237. uint32_t data = 0;
  238. if (reader.get("north", value) && value)
  239. data |= Tile::WORLDMAP_NORTH;
  240. if (reader.get("south", value) && value)
  241. data |= Tile::WORLDMAP_SOUTH;
  242. if (reader.get("west", value) && value)
  243. data |= Tile::WORLDMAP_WEST;
  244. if (reader.get("east", value) && value)
  245. data |= Tile::WORLDMAP_EAST;
  246. if (reader.get("stop", value) && value)
  247. data |= Tile::WORLDMAP_STOP;
  248. reader.get("data", data);
  249. float fps = 10;
  250. reader.get("fps", fps);
  251. std::string object_name, object_data;
  252. reader.get("object-name", object_name);
  253. reader.get("object-data", object_data);
  254. if (reader.get("slope-type", data))
  255. {
  256. attributes |= Tile::SOLID | Tile::SLOPE;
  257. }
  258. std::vector<SurfacePtr> editor_surfaces;
  259. std::optional<ReaderMapping> editor_images_mapping;
  260. if (reader.get("editor-images", editor_images_mapping)) {
  261. editor_surfaces = parse_imagespecs(*editor_images_mapping);
  262. }
  263. std::vector<SurfacePtr> surfaces;
  264. std::optional<ReaderMapping> images_mapping;
  265. if (reader.get("images", images_mapping)) {
  266. surfaces = parse_imagespecs(*images_mapping);
  267. }
  268. bool deprecated = false;
  269. reader.get("deprecated", deprecated);
  270. auto tile = std::make_unique<Tile>(surfaces, editor_surfaces,
  271. attributes, data, fps,
  272. deprecated, object_name, object_data);
  273. m_tileset.add_tile(id, std::move(tile));
  274. }
  275. void
  276. TileSetParser::parse_tiles(const ReaderMapping& reader)
  277. {
  278. // List of ids (use 0 if the tile should be ignored)
  279. std::vector<uint32_t> ids;
  280. // List of attributes of the tile
  281. std::vector<uint32_t> attributes;
  282. // List of data for the tiles
  283. std::vector<uint32_t> datas;
  284. // width and height of the image in tile units, this is used for two
  285. // purposes:
  286. // a) so we don't have to load the image here to know its dimensions
  287. // b) so that the resulting 'tiles' entry is more robust,
  288. // ie. enlarging the image won't break the tile id mapping
  289. // FIXME: height is actually not used, since width might be enough for
  290. // all purposes, still feels somewhat more natural this way
  291. unsigned int width = 0;
  292. unsigned int height = 0;
  293. bool has_ids = reader.get("ids", ids);
  294. bool has_attributes = reader.get("attributes", attributes);
  295. bool has_datas = reader.get("datas", datas);
  296. reader.get("width", width);
  297. reader.get("height", height);
  298. bool shared_surface = false;
  299. reader.get("shared-surface", shared_surface);
  300. float fps = 10;
  301. reader.get("fps", fps);
  302. // Allow specifying additional offset to tiles
  303. int32_t tiles_offset = 0;
  304. reader.get("offset", tiles_offset);
  305. bool deprecated = false;
  306. reader.get("deprecated", deprecated);
  307. if (ids.empty() || !has_ids)
  308. {
  309. throw std::runtime_error("No IDs specified.");
  310. }
  311. if (width == 0)
  312. {
  313. throw std::runtime_error("Width is zero.");
  314. }
  315. else if (height == 0)
  316. {
  317. throw std::runtime_error("Height is zero.");
  318. }
  319. else if (fps < 0)
  320. {
  321. throw std::runtime_error("Negative fps.");
  322. }
  323. else if (ids.size() != width*height)
  324. {
  325. std::ostringstream err;
  326. err << "Number of ids (" << ids.size() << ") and "
  327. "dimensions of image (" << width << "x" << height << " = " << width*height << ") "
  328. "differ";
  329. throw std::runtime_error(err.str());
  330. }
  331. else if (has_attributes && (ids.size() != attributes.size()))
  332. {
  333. std::ostringstream err;
  334. err << "Number of ids (" << ids.size() << ") and attributes (" << attributes.size()
  335. << ") mismatch, but must be equal";
  336. throw std::runtime_error(err.str());
  337. }
  338. else if (has_datas && ids.size() != datas.size())
  339. {
  340. std::ostringstream err;
  341. err << "Number of ids (" << ids.size() << ") and datas (" << datas.size()
  342. << ") mismatch, but must be equal";
  343. throw std::runtime_error(err.str());
  344. }
  345. else
  346. {
  347. if (shared_surface)
  348. {
  349. std::vector<SurfacePtr> editor_surfaces;
  350. std::optional<ReaderMapping> editor_surfaces_mapping;
  351. if (reader.get("editor-images", editor_surfaces_mapping)) {
  352. editor_surfaces = parse_imagespecs(*editor_surfaces_mapping);
  353. }
  354. std::vector<SurfacePtr> surfaces;
  355. std::optional<ReaderMapping> surfaces_mapping;
  356. if (reader.get("image", surfaces_mapping) ||
  357. reader.get("images", surfaces_mapping)) {
  358. surfaces = parse_imagespecs(*surfaces_mapping);
  359. }
  360. for (size_t i = 0; i < ids.size(); ++i)
  361. {
  362. if (!ids[i] || (ids[i] < static_cast<uint32_t>(m_start) || (m_end && ids[i] > static_cast<uint32_t>(m_end)))) continue;
  363. ids[i] += m_offset + tiles_offset;
  364. const int x = static_cast<int>(32 * (i % width));
  365. const int y = static_cast<int>(32 * (i / width));
  366. std::vector<SurfacePtr> regions;
  367. regions.reserve(surfaces.size());
  368. std::transform(surfaces.begin(), surfaces.end(), std::back_inserter(regions),
  369. [x, y] (const SurfacePtr& surface) {
  370. return surface->region(Rect(x, y, Size(32, 32)));
  371. });
  372. std::vector<SurfacePtr> editor_regions;
  373. editor_regions.reserve(editor_surfaces.size());
  374. std::transform(editor_surfaces.begin(), editor_surfaces.end(), std::back_inserter(editor_regions),
  375. [x, y] (const SurfacePtr& surface) {
  376. return surface->region(Rect(x, y, Size(32, 32)));
  377. });
  378. auto tile = std::make_unique<Tile>(regions,
  379. editor_regions,
  380. (has_attributes ? attributes[i] : 0),
  381. (has_datas ? datas[i] : 0),
  382. fps, deprecated);
  383. m_tileset.add_tile(ids[i], std::move(tile));
  384. }
  385. }
  386. else // (!shared_surface)
  387. {
  388. for (size_t i = 0; i < ids.size(); ++i)
  389. {
  390. if(!ids[i] || (ids[i] < static_cast<uint32_t>(m_start) || (m_end && ids[i] > static_cast<uint32_t>(m_end)))) continue;
  391. ids[i] += m_offset + tiles_offset;
  392. int x = static_cast<int>(32 * (i % width));
  393. int y = static_cast<int>(32 * (i / width));
  394. std::vector<SurfacePtr> surfaces;
  395. std::optional<ReaderMapping> surfaces_mapping;
  396. if (reader.get("image", surfaces_mapping) ||
  397. reader.get("images", surfaces_mapping)) {
  398. surfaces = parse_imagespecs(*surfaces_mapping, Rect(x, y, Size(32, 32)));
  399. }
  400. std::vector<SurfacePtr> editor_surfaces;
  401. std::optional<ReaderMapping> editor_surfaces_mapping;
  402. if (reader.get("editor-images", editor_surfaces_mapping)) {
  403. editor_surfaces = parse_imagespecs(*editor_surfaces_mapping, Rect(x, y, Size(32, 32)));
  404. }
  405. auto tile = std::make_unique<Tile>(surfaces,
  406. editor_surfaces,
  407. (has_attributes ? attributes[i] : 0),
  408. (has_datas ? datas[i] : 0),
  409. fps, deprecated);
  410. m_tileset.add_tile(ids[i], std::move(tile));
  411. }
  412. }
  413. }
  414. }
  415. std::vector<SurfacePtr>
  416. TileSetParser::parse_imagespecs(const ReaderMapping& images_mapping,
  417. const std::optional<Rect>& surface_region) const
  418. {
  419. std::vector<SurfacePtr> surfaces;
  420. // (images "foo.png" "foo.bar" ...)
  421. // (images (region "foo.png" 0 0 32 32))
  422. auto iter = images_mapping.get_iter();
  423. while (iter.next())
  424. {
  425. if (iter.is_string())
  426. {
  427. std::string file = iter.as_string_item();
  428. surfaces.push_back(Surface::from_file(FileSystem::join(m_tiles_path, file), surface_region));
  429. }
  430. else if (iter.is_pair() && iter.get_key() == "surface")
  431. {
  432. surfaces.push_back(Surface::from_reader(iter.as_mapping(), surface_region));
  433. }
  434. else if (iter.is_pair() && iter.get_key() == "region")
  435. {
  436. auto const& sx = iter.as_mapping().get_sexp();
  437. auto const& arr = sx.as_array();
  438. if (arr.size() != 6)
  439. {
  440. log_warning << "(region X Y WIDTH HEIGHT) tag malformed: " << sx << std::endl;
  441. }
  442. else
  443. {
  444. const std::string file = arr[1].as_string();
  445. const int x = arr[2].as_int();
  446. const int y = arr[3].as_int();
  447. const int w = arr[4].as_int();
  448. const int h = arr[5].as_int();
  449. Rect rect(x, y, x + w, y + h);
  450. if (surface_region)
  451. {
  452. rect.left += surface_region->left;
  453. rect.top += surface_region->top;
  454. rect.right = rect.left + surface_region->get_width();
  455. rect.bottom = rect.top + surface_region->get_height();
  456. }
  457. surfaces.push_back(Surface::from_file(FileSystem::join(m_tiles_path, file),
  458. rect));
  459. }
  460. }
  461. else
  462. {
  463. log_warning << "Expected string or list in images tag" << std::endl;
  464. }
  465. }
  466. return surfaces;
  467. }
  468. /* EOF */