tile_set_parser.cpp 16 KB

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