tiled_map_reader.gd 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
  1. # The MIT License (MIT)
  2. #
  3. # Copyright (c) 2018 George Marques
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in all
  13. # copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. tool
  23. extends Reference
  24. # Constants for tile flipping
  25. # http://doc.mapeditor.org/reference/tmx-map-format/#tile-flipping
  26. const FLIPPED_HORIZONTALLY_FLAG = 0x80000000
  27. const FLIPPED_VERTICALLY_FLAG = 0x40000000
  28. const FLIPPED_DIAGONALLY_FLAG = 0x20000000
  29. # XML Format reader
  30. const TiledXMLToDictionary = preload("tiled_xml_to_dict.gd")
  31. # Polygon vertices sorter
  32. const PolygonSorter = preload("polygon_sorter.gd")
  33. # Prefix for error messages, make easier to identify the source
  34. const error_prefix = "Tiled Importer: "
  35. # Properties to save the value in the metadata
  36. const whitelist_properties = [
  37. "backgroundcolor",
  38. "compression",
  39. "draworder",
  40. "gid",
  41. "height",
  42. "imageheight",
  43. "imagewidth",
  44. "infinite",
  45. "margin",
  46. "name",
  47. "orientation",
  48. "probability",
  49. "spacing",
  50. "tilecount",
  51. "tiledversion",
  52. "tileheight",
  53. "tilewidth",
  54. "type",
  55. "version",
  56. "visible",
  57. "width",
  58. ]
  59. # All templates loaded, can be looked up by path name
  60. var _loaded_templates = {}
  61. # Maps each tileset file used by the map to it's first gid; Used for template parsing
  62. var _tileset_path_to_first_gid = {}
  63. func reset_global_memebers():
  64. _loaded_templates = {}
  65. _tileset_path_to_first_gid = {}
  66. # Main function
  67. # Reads a source file and gives back a scene
  68. func build(source_path, options):
  69. reset_global_memebers()
  70. var map = read_file(source_path)
  71. if typeof(map) == TYPE_INT:
  72. return map
  73. if typeof(map) != TYPE_DICTIONARY:
  74. return ERR_INVALID_DATA
  75. var err = validate_map(map)
  76. if err != OK:
  77. return err
  78. var cell_size = Vector2(int(map.tilewidth), int(map.tileheight))
  79. var map_mode = TileMap.MODE_SQUARE
  80. var map_offset = TileMap.HALF_OFFSET_DISABLED
  81. var map_pos_offset = Vector2()
  82. var map_background = Color()
  83. var cell_offset = Vector2()
  84. if "orientation" in map:
  85. match map.orientation:
  86. "isometric":
  87. map_mode = TileMap.MODE_ISOMETRIC
  88. "staggered":
  89. map_pos_offset.y -= cell_size.y / 2
  90. match map.staggeraxis:
  91. "x":
  92. map_offset = TileMap.HALF_OFFSET_Y
  93. cell_size.x /= 2.0
  94. if map.staggerindex == "even":
  95. cell_offset.x += 1
  96. map_pos_offset.x -= cell_size.x
  97. "y":
  98. map_offset = TileMap.HALF_OFFSET_X
  99. cell_size.y /= 2.0
  100. if map.staggerindex == "even":
  101. cell_offset.y += 1
  102. map_pos_offset.y -= cell_size.y
  103. "hexagonal":
  104. # Godot maps are always odd and don't have an "even" setting. To
  105. # imitate even staggering we simply start one row/column late and
  106. # adjust the position of the whole map.
  107. match map.staggeraxis:
  108. "x":
  109. map_offset = TileMap.HALF_OFFSET_Y
  110. cell_size.x = int((cell_size.x + map.hexsidelength) / 2)
  111. if map.staggerindex == "even":
  112. cell_offset.x += 1
  113. map_pos_offset.x -= cell_size.x
  114. "y":
  115. map_offset = TileMap.HALF_OFFSET_X
  116. cell_size.y = int((cell_size.y + map.hexsidelength) / 2)
  117. if map.staggerindex == "even":
  118. cell_offset.y += 1
  119. map_pos_offset.y -= cell_size.y
  120. var tileset = build_tileset_for_scene(map.tilesets, source_path, options)
  121. if typeof(tileset) != TYPE_OBJECT:
  122. # Error happened
  123. return tileset
  124. var root = Node2D.new()
  125. root.set_name(source_path.get_file().get_basename())
  126. if options.save_tiled_properties:
  127. set_tiled_properties_as_meta(root, map)
  128. if options.custom_properties:
  129. set_custom_properties(root, map)
  130. var map_data = {
  131. "options": options,
  132. "map_mode": map_mode,
  133. "map_offset": map_offset,
  134. "map_pos_offset": map_pos_offset,
  135. "map_background": map_background,
  136. "cell_size": cell_size,
  137. "cell_offset": cell_offset,
  138. "tileset": tileset,
  139. "source_path": source_path,
  140. "infinite": bool(map.infinite) if "infinite" in map else false
  141. }
  142. for layer in map.layers:
  143. err = make_layer(layer, root, root, map_data)
  144. if err != OK:
  145. return err
  146. if options.add_background and "backgroundcolor" in map:
  147. var bg_color = str(map.backgroundcolor)
  148. if (!bg_color.is_valid_html_color()):
  149. print_error("Invalid background color format: " + bg_color)
  150. return root
  151. map_background = Color(bg_color)
  152. var viewport_size = Vector2(ProjectSettings.get("display/window/size/width"), ProjectSettings.get("display/window/size/height"))
  153. var parbg = ParallaxBackground.new()
  154. var parlayer = ParallaxLayer.new()
  155. var colorizer = ColorRect.new()
  156. parbg.scroll_ignore_camera_zoom = true
  157. parlayer.motion_mirroring = viewport_size
  158. colorizer.color = map_background
  159. colorizer.rect_size = viewport_size
  160. colorizer.rect_min_size = viewport_size
  161. parbg.name = "Background"
  162. root.add_child(parbg)
  163. parbg.owner = root
  164. parlayer.name = "BackgroundLayer"
  165. parbg.add_child(parlayer)
  166. parlayer.owner = root
  167. colorizer.name = "BackgroundColor"
  168. parlayer.add_child(colorizer)
  169. colorizer.owner = root
  170. return root
  171. # Creates a layer node from the data
  172. # Returns an error code
  173. func make_layer(layer, parent, root, data):
  174. var err = validate_layer(layer)
  175. if err != OK:
  176. return err
  177. # Main map data
  178. var map_mode = data.map_mode
  179. var map_offset = data.map_offset
  180. var map_pos_offset = data.map_pos_offset
  181. var cell_size = data.cell_size
  182. var cell_offset = data.cell_offset
  183. var options = data.options
  184. var tileset = data.tileset
  185. var source_path = data.source_path
  186. var infinite = data.infinite
  187. var opacity = float(layer.opacity) if "opacity" in layer else 1.0
  188. var visible = bool(layer.visible) if "visible" in layer else true
  189. if layer.type == "tilelayer":
  190. var layer_size = Vector2(int(layer.width), int(layer.height))
  191. var tilemap = TileMap.new()
  192. tilemap.set_name(str(layer.name))
  193. tilemap.cell_size = cell_size
  194. tilemap.modulate = Color(1.0, 1.0, 1.0, opacity);
  195. tilemap.visible = visible
  196. tilemap.mode = map_mode
  197. tilemap.cell_half_offset = map_offset
  198. tilemap.format = 1
  199. tilemap.cell_clip_uv = options.uv_clip
  200. tilemap.cell_y_sort = true
  201. tilemap.cell_tile_origin = TileMap.TILE_ORIGIN_BOTTOM_LEFT
  202. tilemap.collision_layer = options.collision_layer
  203. var offset = Vector2()
  204. if "offsetx" in layer:
  205. offset.x = int(layer.offsetx)
  206. if "offsety" in layer:
  207. offset.y = int(layer.offsety)
  208. tilemap.position = offset + map_pos_offset
  209. tilemap.tile_set = tileset
  210. var chunks = []
  211. if infinite:
  212. chunks = layer.chunks
  213. else:
  214. chunks = [layer]
  215. for chunk in chunks:
  216. err = validate_chunk(chunk)
  217. if err != OK:
  218. return err
  219. var chunk_data = chunk.data
  220. if "encoding" in layer and layer.encoding == "base64":
  221. if "compression" in layer:
  222. chunk_data = decompress_layer_data(chunk.data, layer.compression, layer_size)
  223. if typeof(chunk_data) == TYPE_INT:
  224. # Error happened
  225. return chunk_data
  226. else:
  227. chunk_data = read_base64_layer_data(chunk.data)
  228. var count = 0
  229. for tile_id in chunk_data:
  230. var int_id = int(str(tile_id)) & 0xFFFFFFFF
  231. if int_id == 0:
  232. count += 1
  233. continue
  234. var flipped_h = bool(int_id & FLIPPED_HORIZONTALLY_FLAG)
  235. var flipped_v = bool(int_id & FLIPPED_VERTICALLY_FLAG)
  236. var flipped_d = bool(int_id & FLIPPED_DIAGONALLY_FLAG)
  237. var gid = int_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  238. var cell_x = cell_offset.x + chunk.x + (count % int(chunk.width))
  239. var cell_y = cell_offset.y + chunk.y + int(count / chunk.width)
  240. tilemap.set_cell(cell_x, cell_y, gid, flipped_h, flipped_v, flipped_d)
  241. count += 1
  242. if options.save_tiled_properties:
  243. set_tiled_properties_as_meta(tilemap, layer)
  244. if options.custom_properties:
  245. set_custom_properties(tilemap, layer)
  246. tilemap.set("editor/display_folded", true)
  247. parent.add_child(tilemap)
  248. tilemap.set_owner(root)
  249. elif layer.type == "imagelayer":
  250. var image = null
  251. if layer.image != "":
  252. image = load_image(layer.image, source_path, options)
  253. if typeof(image) != TYPE_OBJECT:
  254. # Error happened
  255. return image
  256. var pos = Vector2()
  257. var offset = Vector2()
  258. if "x" in layer:
  259. pos.x = float(layer.x)
  260. if "y" in layer:
  261. pos.y = float(layer.y)
  262. if "offsetx" in layer:
  263. offset.x = float(layer.offsetx)
  264. if "offsety" in layer:
  265. offset.y = float(layer.offsety)
  266. var sprite = Sprite.new()
  267. sprite.set_name(str(layer.name))
  268. sprite.centered = false
  269. sprite.texture = image
  270. sprite.visible = visible
  271. sprite.modulate = Color(1.0, 1.0, 1.0, opacity)
  272. if options.save_tiled_properties:
  273. set_tiled_properties_as_meta(sprite, layer)
  274. if options.custom_properties:
  275. set_custom_properties(sprite, layer)
  276. sprite.set("editor/display_folded", true)
  277. parent.add_child(sprite)
  278. sprite.position = pos + offset
  279. sprite.set_owner(root)
  280. elif layer.type == "objectgroup":
  281. var object_layer = Node2D.new()
  282. if options.save_tiled_properties:
  283. set_tiled_properties_as_meta(object_layer, layer)
  284. if options.custom_properties:
  285. set_custom_properties(object_layer, layer)
  286. object_layer.modulate = Color(1.0, 1.0, 1.0, opacity)
  287. object_layer.visible = visible
  288. object_layer.set("editor/display_folded", true)
  289. parent.add_child(object_layer)
  290. object_layer.set_owner(root)
  291. if "name" in layer and not str(layer.name).empty():
  292. object_layer.set_name(str(layer.name))
  293. if not "draworder" in layer or layer.draworder == "topdown":
  294. layer.objects.sort_custom(self, "object_sorter")
  295. for object in layer.objects:
  296. if "template" in object:
  297. var template_file = object["template"]
  298. var template_data_immutable = get_template(remove_filename_from_path(data["source_path"]) + template_file)
  299. if typeof(template_data_immutable) != TYPE_DICTIONARY:
  300. # Error happened
  301. print("Error getting template for object with id " + str(data["id"]))
  302. continue
  303. # Overwrite template data with current object data
  304. apply_template(object, template_data_immutable)
  305. set_default_obj_params(object)
  306. if "point" in object and object.point:
  307. var point = Position2D.new()
  308. if not "x" in object or not "y" in object:
  309. print_error("Missing coordinates for point in object layer.")
  310. continue
  311. point.position = Vector2(float(object.x), float(object.y))
  312. point.visible = bool(object.visible) if "visible" in object else true
  313. object_layer.add_child(point)
  314. point.set_owner(root)
  315. if "name" in object and not str(object.name).empty():
  316. point.set_name(str(object.name))
  317. elif "id" in object and not str(object.id).empty():
  318. point.set_name(str(object.id))
  319. if options.save_tiled_properties:
  320. set_tiled_properties_as_meta(point, object)
  321. if options.custom_properties:
  322. set_custom_properties(point, object)
  323. elif not "gid" in object:
  324. # Not a tile object
  325. if "type" in object and object.type == "navigation":
  326. # Can't make navigation objects right now
  327. print_error("Navigation polygons aren't supported in an object layer.")
  328. continue # Non-fatal error
  329. var shape = shape_from_object(object)
  330. if typeof(shape) != TYPE_OBJECT:
  331. # Error happened
  332. return shape
  333. if "type" in object and object.type == "occluder":
  334. var occluder = LightOccluder2D.new()
  335. var pos = Vector2()
  336. var rot = 0
  337. if "x" in object:
  338. pos.x = float(object.x)
  339. if "y" in object:
  340. pos.y = float(object.y)
  341. if "rotation" in object:
  342. rot = float(object.rotation)
  343. occluder.visible = bool(object.visible) if "visible" in object else true
  344. occluder.position = pos
  345. occluder.rotation_degrees = rot
  346. occluder.occluder = shape
  347. if "name" in object and not str(object.name).empty():
  348. occluder.set_name(str(object.name))
  349. elif "id" in object and not str(object.id).empty():
  350. occluder.set_name(str(object.id))
  351. if options.save_tiled_properties:
  352. set_tiled_properties_as_meta(occluder, object)
  353. if options.custom_properties:
  354. set_custom_properties(occluder, object)
  355. object_layer.add_child(occluder)
  356. occluder.set_owner(root)
  357. else:
  358. var body = Area2D.new() if object.type == "area" else StaticBody2D.new()
  359. var offset = Vector2()
  360. var collision
  361. var pos = Vector2()
  362. var rot = 0
  363. if not ("polygon" in object or "polyline" in object):
  364. # Regular shape
  365. collision = CollisionShape2D.new()
  366. collision.shape = shape
  367. if shape is RectangleShape2D:
  368. offset = shape.extents
  369. elif shape is CircleShape2D:
  370. offset = Vector2(shape.radius, shape.radius)
  371. elif shape is CapsuleShape2D:
  372. offset = Vector2(shape.radius, shape.height)
  373. if shape.radius > shape.height:
  374. var temp = shape.radius
  375. shape.radius = shape.height
  376. shape.height = temp
  377. collision.rotation_degrees = 90
  378. shape.height *= 2
  379. collision.position = offset
  380. else:
  381. collision = CollisionPolygon2D.new()
  382. var points = null
  383. if shape is ConcavePolygonShape2D:
  384. points = []
  385. var segments = shape.segments
  386. for i in range(0, segments.size()):
  387. if i % 2 != 0:
  388. continue
  389. points.push_back(segments[i])
  390. collision.build_mode = CollisionPolygon2D.BUILD_SEGMENTS
  391. else:
  392. points = shape.points
  393. collision.build_mode = CollisionPolygon2D.BUILD_SOLIDS
  394. collision.polygon = points
  395. collision.one_way_collision = object.type == "one-way"
  396. if "x" in object:
  397. pos.x = float(object.x)
  398. if "y" in object:
  399. pos.y = float(object.y)
  400. if "rotation" in object:
  401. rot = float(object.rotation)
  402. body.set("editor/display_folded", true)
  403. object_layer.add_child(body)
  404. body.set_owner(root)
  405. body.add_child(collision)
  406. collision.set_owner(root)
  407. if options.save_tiled_properties:
  408. set_tiled_properties_as_meta(body, object)
  409. if options.custom_properties:
  410. set_custom_properties(body, object)
  411. if "name" in object and not str(object.name).empty():
  412. body.set_name(str(object.name))
  413. elif "id" in object and not str(object.id).empty():
  414. body.set_name(str(object.id))
  415. body.visible = bool(object.visible) if "visible" in object else true
  416. body.position = pos
  417. body.rotation_degrees = rot
  418. else: # "gid" in object
  419. var tile_raw_id = int(str(object.gid)) & 0xFFFFFFFF
  420. var tile_id = tile_raw_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  421. var is_tile_object = tileset.tile_get_region(tile_id).get_area() == 0
  422. var collisions = tileset.tile_get_shape_count(tile_id)
  423. var has_collisions = collisions > 0 && object.has("type") && object.type != "sprite"
  424. var sprite = Sprite.new()
  425. var pos = Vector2()
  426. var rot = 0
  427. var scale = Vector2(1, 1)
  428. sprite.texture = tileset.tile_get_texture(tile_id)
  429. var texture_size = sprite.texture.get_size() if sprite.texture != null else Vector2()
  430. if not is_tile_object:
  431. sprite.region_enabled = true
  432. sprite.region_rect = tileset.tile_get_region(tile_id)
  433. texture_size = tileset.tile_get_region(tile_id).size
  434. sprite.flip_h = bool(tile_raw_id & FLIPPED_HORIZONTALLY_FLAG)
  435. sprite.flip_v = bool(tile_raw_id & FLIPPED_VERTICALLY_FLAG)
  436. if "x" in object:
  437. pos.x = float(object.x)
  438. if "y" in object:
  439. pos.y = float(object.y)
  440. if "rotation" in object:
  441. rot = float(object.rotation)
  442. if texture_size != Vector2():
  443. if "width" in object and float(object.width) != texture_size.x:
  444. scale.x = float(object.width) / texture_size.x
  445. if "height" in object and float(object.height) != texture_size.y:
  446. scale.y = float(object.height) / texture_size.y
  447. var obj_root = sprite
  448. if has_collisions:
  449. match object.type:
  450. "area": obj_root = Area2D.new()
  451. "kinematic": obj_root = KinematicBody2D.new()
  452. "rigid": obj_root = RigidBody2D.new()
  453. _: obj_root = StaticBody2D.new()
  454. object_layer.add_child(obj_root)
  455. obj_root.owner = root
  456. obj_root.add_child(sprite)
  457. sprite.owner = root
  458. var shapes = tileset.tile_get_shapes(tile_id)
  459. for s in shapes:
  460. var collision_node = CollisionShape2D.new()
  461. collision_node.shape = s.shape
  462. collision_node.transform = s.shape_transform
  463. if sprite.flip_h:
  464. collision_node.position.x *= -1
  465. collision_node.position.x -= cell_size.x
  466. collision_node.scale.x *= -1
  467. if sprite.flip_v:
  468. collision_node.scale.y *= -1
  469. collision_node.position.y *= -1
  470. collision_node.position.y -= cell_size.y
  471. obj_root.add_child(collision_node)
  472. collision_node.owner = root
  473. if "name" in object and not str(object.name).empty():
  474. obj_root.set_name(str(object.name))
  475. elif "id" in object and not str(object.id).empty():
  476. obj_root.set_name(str(object.id))
  477. obj_root.position = pos
  478. obj_root.rotation_degrees = rot
  479. obj_root.visible = bool(object.visible) if "visible" in object else true
  480. obj_root.scale = scale
  481. # Translate from Tiled bottom-left position to Godot top-left
  482. sprite.centered = false
  483. sprite.region_filter_clip = options.uv_clip
  484. sprite.offset = Vector2(0, -texture_size.y)
  485. if not has_collisions:
  486. object_layer.add_child(sprite)
  487. sprite.set_owner(root)
  488. if options.save_tiled_properties:
  489. set_tiled_properties_as_meta(obj_root, object)
  490. if options.custom_properties:
  491. if options.tile_metadata:
  492. var tile_meta = tileset.get_meta("tile_meta")
  493. if typeof(tile_meta) == TYPE_DICTIONARY and tile_id in tile_meta:
  494. for prop in tile_meta[tile_id]:
  495. obj_root.set_meta(prop, tile_meta[tile_id][prop])
  496. set_custom_properties(obj_root, object)
  497. elif layer.type == "group":
  498. var group = Node2D.new()
  499. var pos = Vector2()
  500. if "x" in layer:
  501. pos.x = float(layer.x)
  502. if "y" in layer:
  503. pos.y = float(layer.y)
  504. group.modulate = Color(1.0, 1.0, 1.0, opacity)
  505. group.visible = visible
  506. group.position = pos
  507. if options.save_tiled_properties:
  508. set_tiled_properties_as_meta(group, layer)
  509. if options.custom_properties:
  510. set_custom_properties(group, layer)
  511. if "name" in layer and not str(layer.name).empty():
  512. group.set_name(str(layer.name))
  513. group.set("editor/display_folded", true)
  514. parent.add_child(group)
  515. group.set_owner(root)
  516. for sub_layer in layer.layers:
  517. make_layer(sub_layer, group, root, data)
  518. else:
  519. print_error("Unknown layer type ('%s') in '%s'" % [str(layer.type), str(layer.name) if "name" in layer else "[unnamed layer]"])
  520. return ERR_INVALID_DATA
  521. return OK
  522. func set_default_obj_params(object):
  523. # Set default values for object
  524. for attr in ["width", "height", "rotation", "x", "y"]:
  525. if not attr in object:
  526. object[attr] = 0
  527. if not "type" in object:
  528. object.type = ""
  529. if not "visible" in object:
  530. object.visible = true
  531. # Makes a tileset from a array of tilesets data
  532. # Since Godot supports only one TileSet per TileMap, all tilesets from Tiled are combined
  533. func build_tileset_for_scene(tilesets, source_path, options):
  534. var result = TileSet.new()
  535. var err = ERR_INVALID_DATA
  536. var tile_meta = {}
  537. for tileset in tilesets:
  538. var ts = tileset
  539. var ts_source_path = source_path
  540. if "source" in ts:
  541. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_integer():
  542. print_error("Missing or invalid firstgid tileset property.")
  543. return ERR_INVALID_DATA
  544. ts_source_path = source_path.get_base_dir().plus_file(ts.source)
  545. # Used later for templates
  546. _tileset_path_to_first_gid[ts_source_path] = tileset.firstgid
  547. if ts.source.get_extension().to_lower() == "tsx":
  548. var tsx_reader = TiledXMLToDictionary.new()
  549. ts = tsx_reader.read_tsx(ts_source_path)
  550. if typeof(ts) != TYPE_DICTIONARY:
  551. # Error happened
  552. return ts
  553. else: # JSON Tileset
  554. var f = File.new()
  555. err = f.open(ts_source_path, File.READ)
  556. if err != OK:
  557. print_error("Error opening tileset '%s'." % [ts.source])
  558. return err
  559. var json_res = JSON.parse(f.get_as_text())
  560. if json_res.error != OK:
  561. print_error("Error parsing tileset '%s' JSON: %s" % [ts.source, json_res.error_string])
  562. return ERR_INVALID_DATA
  563. ts = json_res.result
  564. if typeof(ts) != TYPE_DICTIONARY:
  565. print_error("Tileset '%s' is not a dictionary." % [ts.source])
  566. return ERR_INVALID_DATA
  567. ts.firstgid = tileset.firstgid
  568. err = validate_tileset(ts)
  569. if err != OK:
  570. return err
  571. var has_global_image = "image" in ts
  572. var spacing = int(ts.spacing) if "spacing" in ts and str(ts.spacing).is_valid_integer() else 0
  573. var margin = int(ts.margin) if "margin" in ts and str(ts.margin).is_valid_integer() else 0
  574. var firstgid = int(ts.firstgid)
  575. var columns = int(ts.columns) if "columns" in ts and str(ts.columns).is_valid_integer() else -1
  576. var image = null
  577. var imagesize = Vector2()
  578. if has_global_image:
  579. image = load_image(ts.image, ts_source_path, options)
  580. if typeof(image) != TYPE_OBJECT:
  581. # Error happened
  582. return image
  583. imagesize = Vector2(int(ts.imagewidth), int(ts.imageheight))
  584. var tilesize = Vector2(int(ts.tilewidth), int(ts.tileheight))
  585. var tilecount = int(ts.tilecount)
  586. var gid = firstgid
  587. var x = margin
  588. var y = margin
  589. var i = 0
  590. var column = 0
  591. while i < tilecount:
  592. var tilepos = Vector2(x, y)
  593. var region = Rect2(tilepos, tilesize)
  594. var rel_id = str(gid - firstgid)
  595. result.create_tile(gid)
  596. if has_global_image:
  597. result.tile_set_texture(gid, image)
  598. result.tile_set_region(gid, region)
  599. if options.apply_offset:
  600. result.tile_set_texture_offset(gid, Vector2(0, -tilesize.y))
  601. elif not rel_id in ts.tiles:
  602. gid += 1
  603. continue
  604. else:
  605. var image_path = ts.tiles[rel_id].image
  606. image = load_image(image_path, ts_source_path, options)
  607. if typeof(image) != TYPE_OBJECT:
  608. # Error happened
  609. return image
  610. result.tile_set_texture(gid, image)
  611. if options.apply_offset:
  612. result.tile_set_texture_offset(gid, Vector2(0, -image.get_height()))
  613. if "tiles" in ts and rel_id in ts.tiles and "objectgroup" in ts.tiles[rel_id] \
  614. and "objects" in ts.tiles[rel_id].objectgroup:
  615. for object in ts.tiles[rel_id].objectgroup.objects:
  616. var shape = shape_from_object(object)
  617. if typeof(shape) != TYPE_OBJECT:
  618. # Error happened
  619. return shape
  620. var offset = Vector2(float(object.x), float(object.y))
  621. if options.apply_offset:
  622. offset += result.tile_get_texture_offset(gid)
  623. if "width" in object and "height" in object:
  624. offset += Vector2(float(object.width) / 2, float(object.height) / 2)
  625. if object.type == "navigation":
  626. result.tile_set_navigation_polygon(gid, shape)
  627. result.tile_set_navigation_polygon_offset(gid, offset)
  628. elif object.type == "occluder":
  629. result.tile_set_light_occluder(gid, shape)
  630. result.tile_set_occluder_offset(gid, offset)
  631. else:
  632. result.tile_add_shape(gid, shape, Transform2D(0, offset), object.type == "one-way")
  633. if options.custom_properties and options.tile_metadata and "tileproperties" in ts \
  634. and "tilepropertytypes" in ts and rel_id in ts.tileproperties and rel_id in ts.tilepropertytypes:
  635. tile_meta[gid] = get_custom_properties(ts.tileproperties[rel_id], ts.tilepropertytypes[rel_id])
  636. if options.save_tiled_properties and rel_id in ts.tiles:
  637. for property in whitelist_properties:
  638. if property in ts.tiles[rel_id]:
  639. if not gid in tile_meta: tile_meta[gid] = {}
  640. tile_meta[gid][property] = ts.tiles[rel_id][property]
  641. gid += 1
  642. column += 1
  643. i += 1
  644. x += int(tilesize.x) + spacing
  645. if (columns > 0 and column >= columns) or x >= int(imagesize.x) - margin or (x + int(tilesize.x)) > int(imagesize.x):
  646. x = margin
  647. y += int(tilesize.y) + spacing
  648. column = 0
  649. if str(ts.name) != "":
  650. result.resource_name = str(ts.name)
  651. if options.save_tiled_properties:
  652. set_tiled_properties_as_meta(result, ts)
  653. if options.custom_properties:
  654. if "properties" in ts and "propertytypes" in ts:
  655. set_custom_properties(result, ts)
  656. if options.custom_properties and options.tile_metadata:
  657. result.set_meta("tile_meta", tile_meta)
  658. return result
  659. # Makes a standalone TileSet. Useful for importing TileSets from Tiled
  660. # Returns an error code if fails
  661. func build_tileset(source_path, options):
  662. var set = read_tileset_file(source_path)
  663. if typeof(set) == TYPE_INT:
  664. return set
  665. if typeof(set) != TYPE_DICTIONARY:
  666. return ERR_INVALID_DATA
  667. # Just to validate and build correctly using the existing builder
  668. set["firstgid"] = 0
  669. return build_tileset_for_scene([set], source_path, options)
  670. # Loads an image from a given path
  671. # Returns a Texture
  672. func load_image(rel_path, source_path, options):
  673. var flags = options.image_flags if "image_flags" in options else Texture.FLAGS_DEFAULT
  674. var embed = options.embed_internal_images if "embed_internal_images" in options else false
  675. var ext = rel_path.get_extension().to_lower()
  676. if ext != "png" and ext != "jpg":
  677. print_error("Unsupported image format: %s. Use PNG or JPG instead." % [ext])
  678. return ERR_FILE_UNRECOGNIZED
  679. var total_path = rel_path
  680. if rel_path.is_rel_path():
  681. total_path = ProjectSettings.globalize_path(source_path.get_base_dir()).plus_file(rel_path)
  682. total_path = ProjectSettings.localize_path(total_path)
  683. var dir = Directory.new()
  684. if not dir.file_exists(total_path):
  685. print_error("Image not found: %s" % [total_path])
  686. return ERR_FILE_NOT_FOUND
  687. if not total_path.begins_with("res://"):
  688. # External images need to be embedded
  689. embed = true
  690. var image = null
  691. if embed:
  692. image = ImageTexture.new()
  693. image.load(total_path)
  694. else:
  695. image = ResourceLoader.load(total_path, "ImageTexture")
  696. if image != null:
  697. image.set_flags(flags)
  698. return image
  699. # Reads a file and returns its contents as a dictionary
  700. # Returns an error code if fails
  701. func read_file(path):
  702. if path.get_extension().to_lower() == "tmx":
  703. var tmx_to_dict = TiledXMLToDictionary.new()
  704. var data = tmx_to_dict.read_tmx(path)
  705. if typeof(data) != TYPE_DICTIONARY:
  706. # Error happened
  707. print_error("Error parsing map file '%s'." % [path])
  708. # Return error or result
  709. return data
  710. # Not TMX, must be JSON
  711. var file = File.new()
  712. var err = file.open(path, File.READ)
  713. if err != OK:
  714. return err
  715. var content = JSON.parse(file.get_as_text())
  716. if content.error != OK:
  717. print_error("Error parsing JSON: " + content.error_string)
  718. return content.error
  719. return content.result
  720. # Reads a tileset file and return its contents as a dictionary
  721. # Returns an error code if fails
  722. func read_tileset_file(path):
  723. if path.get_extension().to_lower() == "tsx":
  724. var tmx_to_dict = TiledXMLToDictionary.new()
  725. var data = tmx_to_dict.read_tsx(path)
  726. if typeof(data) != TYPE_DICTIONARY:
  727. # Error happened
  728. print_error("Error parsing map file '%s'." % [path])
  729. # Return error or result
  730. return data
  731. # Not TSX, must be JSON
  732. var file = File.new()
  733. var err = file.open(path, File.READ)
  734. if err != OK:
  735. return err
  736. var content = JSON.parse(file.get_as_text())
  737. if content.error != OK:
  738. print_error("Error parsing JSON: " + content.error_string)
  739. return content.error
  740. return content.result
  741. # Creates a shape from an object data
  742. # Returns a valid shape depending on the object type (collision/occluder/navigation)
  743. func shape_from_object(object):
  744. var shape = ERR_INVALID_DATA
  745. set_default_obj_params(object)
  746. if "polygon" in object or "polyline" in object:
  747. var vertices = PoolVector2Array()
  748. if "polygon" in object:
  749. for point in object.polygon:
  750. vertices.push_back(Vector2(float(point.x), float(point.y)))
  751. else:
  752. for point in object.polyline:
  753. vertices.push_back(Vector2(float(point.x), float(point.y)))
  754. if object.type == "navigation":
  755. shape = NavigationPolygon.new()
  756. shape.vertices = vertices
  757. shape.add_outline(vertices)
  758. shape.make_polygons_from_outlines()
  759. elif object.type == "occluder":
  760. shape = OccluderPolygon2D.new()
  761. shape.polygon = vertices
  762. shape.closed = "polygon" in object
  763. else:
  764. if is_convex(vertices):
  765. var sorter = PolygonSorter.new()
  766. vertices = sorter.sort_polygon(vertices)
  767. shape = ConvexPolygonShape2D.new()
  768. shape.points = vertices
  769. else:
  770. shape = ConcavePolygonShape2D.new()
  771. var segments = [vertices[0]]
  772. for x in range(1, vertices.size()):
  773. segments.push_back(vertices[x])
  774. segments.push_back(vertices[x])
  775. segments.push_back(vertices[0])
  776. shape.segments = PoolVector2Array(segments)
  777. elif "ellipse" in object:
  778. if object.type == "navigation" or object.type == "occluder":
  779. print_error("Ellipse shapes are not supported as navigation or occluder. Use polygon/polyline instead.")
  780. return ERR_INVALID_DATA
  781. if not "width" in object or not "height" in object:
  782. print_error("Missing width or height in ellipse shape.")
  783. return ERR_INVALID_DATA
  784. var w = abs(float(object.width))
  785. var h = abs(float(object.height))
  786. if w == h:
  787. shape = CircleShape2D.new()
  788. shape.radius = w / 2.0
  789. else:
  790. # Using a capsule since it's the closest from an ellipse
  791. shape = CapsuleShape2D.new()
  792. shape.radius = w / 2.0
  793. shape.height = h / 2.0
  794. else: # Rectangle
  795. if not "width" in object or not "height" in object:
  796. print_error("Missing width or height in rectangle shape.")
  797. return ERR_INVALID_DATA
  798. var size = Vector2(float(object.width), float(object.height))
  799. if object.type == "navigation" or object.type == "occluder":
  800. # Those types only accept polygons, so make one from the rectangle
  801. var vertices = PoolVector2Array([
  802. Vector2(0, 0),
  803. Vector2(size.x, 0),
  804. size,
  805. Vector2(0, size.y)
  806. ])
  807. if object.type == "navigation":
  808. shape = NavigationPolygon.new()
  809. shape.vertices = vertices
  810. shape.add_outline(vertices)
  811. shape.make_polygons_from_outlines()
  812. else:
  813. shape = OccluderPolygon2D.new()
  814. shape.polygon = vertices
  815. else:
  816. shape = RectangleShape2D.new()
  817. shape.extents = size / 2.0
  818. return shape
  819. # Determines if the set of vertices is convex or not
  820. # Returns a boolean
  821. func is_convex(vertices):
  822. var size = vertices.size()
  823. if size <= 3:
  824. # Less than 3 verices can't be concave
  825. return true
  826. var cp = 0
  827. for i in range(0, size + 2):
  828. var p1 = vertices[(i + 0) % size]
  829. var p2 = vertices[(i + 1) % size]
  830. var p3 = vertices[(i + 2) % size]
  831. var prev_cp = cp
  832. cp = (p2.x - p1.x) * (p3.y - p2.y) - (p2.y - p1.y) * (p3.x - p2.x)
  833. if i > 0 and sign(cp) != sign(prev_cp):
  834. return false
  835. return true
  836. # Decompress the data of the layer
  837. # Compression argument is a string, either "gzip" or "zlib"
  838. func decompress_layer_data(layer_data, compression, map_size):
  839. if compression != "gzip" and compression != "zlib":
  840. print_error("Unrecognized compression format: %s" % [compression])
  841. return ERR_INVALID_DATA
  842. var compression_type = File.COMPRESSION_DEFLATE if compression == "zlib" else File.COMPRESSION_GZIP
  843. var expected_size = int(map_size.x) * int(map_size.y) * 4
  844. var raw_data = Marshalls.base64_to_raw(layer_data).decompress(expected_size, compression_type)
  845. return decode_layer(raw_data)
  846. # Reads the layer as a base64 data
  847. # Returns an array of ints as the decoded layer would be
  848. func read_base64_layer_data(layer_data):
  849. var decoded = Marshalls.base64_to_raw(layer_data)
  850. return decode_layer(decoded)
  851. # Reads a PoolByteArray and returns the layer array
  852. # Used for base64 encoded and compressed layers
  853. func decode_layer(layer_data):
  854. var result = []
  855. for i in range(0, layer_data.size(), 4):
  856. var num = (layer_data[i]) | \
  857. (layer_data[i + 1] << 8) | \
  858. (layer_data[i + 2] << 16) | \
  859. (layer_data[i + 3] << 24)
  860. result.push_back(num)
  861. return result
  862. # Set the custom properties into the metadata of the object
  863. func set_custom_properties(object, tiled_object):
  864. if not "properties" in tiled_object or not "propertytypes" in tiled_object:
  865. return
  866. var properties = get_custom_properties(tiled_object.properties, tiled_object.propertytypes)
  867. for property in properties:
  868. object.set_meta(property, properties[property])
  869. # Get the custom properties as a dictionary
  870. # Useful for tile meta, which is not stored directly
  871. func get_custom_properties(properties, types):
  872. var result = {}
  873. for property in properties:
  874. var value = null
  875. if str(types[property]).to_lower() == "bool":
  876. value = bool(properties[property])
  877. elif str(types[property]).to_lower() == "int":
  878. value = int(properties[property])
  879. elif str(types[property]).to_lower() == "float":
  880. value = float(properties[property])
  881. elif str(types[property]).to_lower() == "color":
  882. value = Color(properties[property])
  883. else:
  884. value = str(properties[property])
  885. result[property] = value
  886. return result
  887. # Get the available whitelisted properties from the Tiled object
  888. # And them as metadata in the Godot object
  889. func set_tiled_properties_as_meta(object, tiled_object):
  890. for property in whitelist_properties:
  891. if property in tiled_object:
  892. object.set_meta(property, tiled_object[property])
  893. # Custom function to sort objects in an object layer
  894. # This is done to support the "topdown" draw order, which sorts by 'y' coordinate
  895. func object_sorter(first, second):
  896. if first.y == second.y:
  897. return first.id < second.id
  898. return first.y < second.y
  899. # Validates the map dictionary content for missing or invalid keys
  900. # Returns an error code
  901. func validate_map(map):
  902. if not "type" in map or map.type != "map":
  903. print_error("Missing or invalid type property.")
  904. return ERR_INVALID_DATA
  905. elif not "version" in map or int(map.version) != 1:
  906. print_error("Missing or invalid map version.")
  907. return ERR_INVALID_DATA
  908. elif not "tileheight" in map or not str(map.tileheight).is_valid_integer():
  909. print_error("Missing or invalid tileheight property.")
  910. return ERR_INVALID_DATA
  911. elif not "tilewidth" in map or not str(map.tilewidth).is_valid_integer():
  912. print_error("Missing or invalid tilewidth property.")
  913. return ERR_INVALID_DATA
  914. elif not "layers" in map or typeof(map.layers) != TYPE_ARRAY:
  915. print_error("Missing or invalid layers property.")
  916. return ERR_INVALID_DATA
  917. elif not "tilesets" in map or typeof(map.tilesets) != TYPE_ARRAY:
  918. print_error("Missing or invalid tilesets property.")
  919. return ERR_INVALID_DATA
  920. if "orientation" in map and (map.orientation == "staggered" or map.orientation == "hexagonal"):
  921. if not "staggeraxis" in map:
  922. print_error("Missing stagger axis property.")
  923. return ERR_INVALID_DATA
  924. elif not "staggerindex" in map:
  925. print_error("Missing stagger axis property.")
  926. return ERR_INVALID_DATA
  927. return OK
  928. # Validates the tileset dictionary content for missing or invalid keys
  929. # Returns an error code
  930. func validate_tileset(tileset):
  931. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_integer():
  932. print_error("Missing or invalid firstgid tileset property.")
  933. return ERR_INVALID_DATA
  934. elif not "tilewidth" in tileset or not str(tileset.tilewidth).is_valid_integer():
  935. print_error("Missing or invalid tilewidth tileset property.")
  936. return ERR_INVALID_DATA
  937. elif not "tileheight" in tileset or not str(tileset.tileheight).is_valid_integer():
  938. print_error("Missing or invalid tileheight tileset property.")
  939. return ERR_INVALID_DATA
  940. elif not "tilecount" in tileset or not str(tileset.tilecount).is_valid_integer():
  941. print_error("Missing or invalid tilecount tileset property.")
  942. return ERR_INVALID_DATA
  943. if not "image" in tileset:
  944. for tile in tileset.tiles:
  945. if not "image" in tileset.tiles[tile]:
  946. print_error("Missing or invalid image in tileset property.")
  947. return ERR_INVALID_DATA
  948. elif not "imagewidth" in tileset.tiles[tile] or not str(tileset.tiles[tile].imagewidth).is_valid_integer():
  949. print_error("Missing or invalid imagewidth tileset property 1.")
  950. return ERR_INVALID_DATA
  951. elif not "imageheight" in tileset.tiles[tile] or not str(tileset.tiles[tile].imageheight).is_valid_integer():
  952. print_error("Missing or invalid imageheight tileset property.")
  953. return ERR_INVALID_DATA
  954. else:
  955. if not "imagewidth" in tileset or not str(tileset.imagewidth).is_valid_integer():
  956. print_error("Missing or invalid imagewidth tileset property 2.")
  957. return ERR_INVALID_DATA
  958. elif not "imageheight" in tileset or not str(tileset.imageheight).is_valid_integer():
  959. print_error("Missing or invalid imageheight tileset property.")
  960. return ERR_INVALID_DATA
  961. return OK
  962. # Validates the layer dictionary content for missing or invalid keys
  963. # Returns an error code
  964. func validate_layer(layer):
  965. if not "type" in layer:
  966. print_error("Missing or invalid type layer property.")
  967. return ERR_INVALID_DATA
  968. elif not "name" in layer:
  969. print_error("Missing or invalid name layer property.")
  970. return ERR_INVALID_DATA
  971. match layer.type:
  972. "tilelayer":
  973. if not "height" in layer or not str(layer.height).is_valid_integer():
  974. print_error("Missing or invalid layer height property.")
  975. return ERR_INVALID_DATA
  976. elif not "width" in layer or not str(layer.width).is_valid_integer():
  977. print_error("Missing or invalid layer width property.")
  978. return ERR_INVALID_DATA
  979. elif not "data" in layer:
  980. if not "chunks" in layer:
  981. print_error("Missing data or chunks layer properties.")
  982. return ERR_INVALID_DATA
  983. elif typeof(layer.chunks) != TYPE_ARRAY:
  984. print_error("Invalid chunks layer property.")
  985. return ERR_INVALID_DATA
  986. elif "encoding" in layer:
  987. if layer.encoding == "base64" and typeof(layer.data) != TYPE_STRING:
  988. print_error("Invalid data layer property.")
  989. return ERR_INVALID_DATA
  990. if layer.encoding != "base64" and typeof(layer.data) != TYPE_ARRAY:
  991. print_error("Invalid data layer property.")
  992. return ERR_INVALID_DATA
  993. elif typeof(layer.data) != TYPE_ARRAY:
  994. print_error("Invalid data layer property.")
  995. return ERR_INVALID_DATA
  996. if "compression" in layer:
  997. if layer.compression != "gzip" and layer.compression != "zlib":
  998. print_error("Invalid compression type.")
  999. return ERR_INVALID_DATA
  1000. "imagelayer":
  1001. if not "image" in layer or typeof(layer.image) != TYPE_STRING:
  1002. print_error("Missing or invalid image path for layer.")
  1003. return ERR_INVALID_DATA
  1004. "objectgroup":
  1005. if not "objects" in layer or typeof(layer.objects) != TYPE_ARRAY:
  1006. print_error("Missing or invalid objects array for layer.")
  1007. return ERR_INVALID_DATA
  1008. "group":
  1009. if not "layers" in layer or typeof(layer.layers) != TYPE_ARRAY:
  1010. print_error("Missing or invalid layer array for group layer.")
  1011. return ERR_INVALID_DATA
  1012. return OK
  1013. func validate_chunk(chunk):
  1014. if not "data" in chunk:
  1015. print_error("Missing data chunk property.")
  1016. return ERR_INVALID_DATA
  1017. elif not "height" in chunk or not str(chunk.height).is_valid_integer():
  1018. print_error("Missing or invalid height chunk property.")
  1019. return ERR_INVALID_DATA
  1020. elif not "width" in chunk or not str(chunk.width).is_valid_integer():
  1021. print_error("Missing or invalid width chunk property.")
  1022. return ERR_INVALID_DATA
  1023. elif not "x" in chunk or not str(chunk.x).is_valid_integer():
  1024. print_error("Missing or invalid x chunk property.")
  1025. return ERR_INVALID_DATA
  1026. elif not "y" in chunk or not str(chunk.y).is_valid_integer():
  1027. print_error("Missing or invalid y chunk property.")
  1028. return ERR_INVALID_DATA
  1029. return OK
  1030. # Custom function to print error, to centralize the prefix addition
  1031. func print_error(err):
  1032. printerr(error_prefix + err)
  1033. func get_template(path):
  1034. # If this template has not yet been loaded
  1035. if not _loaded_templates.has(path):
  1036. # IS XML
  1037. if path.get_extension().to_lower() == "tx":
  1038. var parser = XMLParser.new()
  1039. var err = parser.open(path)
  1040. if err != OK:
  1041. print_error("Error opening TX file '%s'." % [path])
  1042. return err
  1043. var content = parse_template(parser, path)
  1044. if typeof(content) != TYPE_DICTIONARY:
  1045. # Error happened
  1046. print_error("Error parsing template map file '%s'." % [path])
  1047. return false
  1048. _loaded_templates[path] = content
  1049. # IS JSON
  1050. else:
  1051. var file = File.new()
  1052. var err = file.open(path, File.READ)
  1053. if err != OK:
  1054. return err
  1055. var json_res = JSON.parse(file.get_as_text())
  1056. if json_res.error != OK:
  1057. print_error("Error parsing JSON template map file '%s'." % [path])
  1058. return json_res.error
  1059. var result = json_res.result
  1060. if typeof(result) != TYPE_DICTIONARY:
  1061. print_error("Error parsing JSON template map file '%s'." % [path])
  1062. return ERR_INVALID_DATA
  1063. var object = result.object
  1064. if object.has("gid"):
  1065. if result.has("tileset"):
  1066. var ts_path = remove_filename_from_path(path) + result.tileset.source
  1067. var tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1068. object.gid += tileset_gid_increment
  1069. _loaded_templates[path] = object
  1070. var dict = _loaded_templates[path]
  1071. var dictCopy = {}
  1072. for k in dict:
  1073. dictCopy[k] = dict[k]
  1074. return dictCopy
  1075. func parse_template(parser, path):
  1076. var err = OK
  1077. # Template root node shouldn't have attributes
  1078. var data = {}
  1079. var tileset_gid_increment = 0
  1080. data.id = 0
  1081. err = parser.read()
  1082. while err == OK:
  1083. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  1084. if parser.get_node_name() == "template":
  1085. break
  1086. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  1087. if parser.get_node_name() == "tileset":
  1088. var ts_path = remove_filename_from_path(path) + parser.get_named_attribute_value_safe("source")
  1089. tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1090. data.tileset = ts_path
  1091. if parser.get_node_name() == "object":
  1092. var object = TiledXMLToDictionary.parse_object(parser)
  1093. for k in object:
  1094. data[k] = object[k]
  1095. err = parser.read()
  1096. if data.has("gid"):
  1097. data["gid"] += tileset_gid_increment
  1098. return data
  1099. func get_first_gid_from_tileset_path(path):
  1100. for t in _tileset_path_to_first_gid:
  1101. if is_same_file(path, t):
  1102. return _tileset_path_to_first_gid[t]
  1103. return 0
  1104. static func get_filename_from_path(path):
  1105. var substrings = path.split("/", false)
  1106. var file_name = substrings[substrings.size() - 1]
  1107. return file_name
  1108. static func remove_filename_from_path(path):
  1109. var file_name = get_filename_from_path(path)
  1110. var stringSize = path.length() - file_name.length()
  1111. var file_path = path.substr(0,stringSize)
  1112. return file_path
  1113. static func is_same_file(path1, path2):
  1114. var file1 = File.new()
  1115. var err = file1.open(path1, File.READ)
  1116. if err != OK:
  1117. return err
  1118. var file2 = File.new()
  1119. err = file2.open(path2, File.READ)
  1120. if err != OK:
  1121. return err
  1122. var file1_str = file1.get_as_text()
  1123. var file2_str = file2.get_as_text()
  1124. if file1_str == file2_str:
  1125. return true
  1126. return false
  1127. static func apply_template(object, template_immutable):
  1128. for k in template_immutable:
  1129. # Do not overwrite any object data
  1130. if typeof(template_immutable[k]) == TYPE_DICTIONARY:
  1131. if not object.has(k):
  1132. object[k] = {}
  1133. apply_template(object[k], template_immutable[k])
  1134. elif not object.has(k):
  1135. object[k] = template_immutable[k]