Newer
Older
DungeonShooting / addons / vnen.tiled_importer / tiled_map_reader.gd
@小李xl 小李xl on 21 May 2022 44 KB 安装Tiled插件
  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.  
  23. tool
  24. extends Reference
  25.  
  26. # Constants for tile flipping
  27. # http://doc.mapeditor.org/reference/tmx-map-format/#tile-flipping
  28. const FLIPPED_HORIZONTALLY_FLAG = 0x80000000
  29. const FLIPPED_VERTICALLY_FLAG = 0x40000000
  30. const FLIPPED_DIAGONALLY_FLAG = 0x20000000
  31.  
  32. # XML Format reader
  33. const TiledXMLToDictionary = preload("tiled_xml_to_dict.gd")
  34.  
  35. # Polygon vertices sorter
  36. const PolygonSorter = preload("polygon_sorter.gd")
  37.  
  38. # Prefix for error messages, make easier to identify the source
  39. const error_prefix = "Tiled Importer: "
  40.  
  41. # Properties to save the value in the metadata
  42. const whitelist_properties = [
  43. "backgroundcolor",
  44. "compression",
  45. "draworder",
  46. "gid",
  47. "height",
  48. "imageheight",
  49. "imagewidth",
  50. "infinite",
  51. "margin",
  52. "name",
  53. "offsetx",
  54. "offsety",
  55. "orientation",
  56. "probability",
  57. "spacing",
  58. "tilecount",
  59. "tiledversion",
  60. "tileheight",
  61. "tilewidth",
  62. "type",
  63. "version",
  64. "visible",
  65. "width",
  66. "custom_material"
  67. ]
  68.  
  69. # All templates loaded, can be looked up by path name
  70. var _loaded_templates = {}
  71. # Maps each tileset file used by the map to it's first gid; Used for template parsing
  72. var _tileset_path_to_first_gid = {}
  73.  
  74. func reset_global_memebers():
  75. _loaded_templates = {}
  76. _tileset_path_to_first_gid = {}
  77.  
  78. # Main function
  79. # Reads a source file and gives back a scene
  80. func build(source_path, options):
  81. reset_global_memebers()
  82. var map = read_file(source_path)
  83. if typeof(map) == TYPE_INT:
  84. return map
  85. if typeof(map) != TYPE_DICTIONARY:
  86. return ERR_INVALID_DATA
  87.  
  88. var err = validate_map(map)
  89. if err != OK:
  90. return err
  91.  
  92. var cell_size = Vector2(int(map.tilewidth), int(map.tileheight))
  93. var map_mode = TileMap.MODE_SQUARE
  94. var map_offset = TileMap.HALF_OFFSET_DISABLED
  95. var map_pos_offset = Vector2()
  96. var map_background = Color()
  97. var cell_offset = Vector2()
  98. if "orientation" in map:
  99. match map.orientation:
  100. "isometric":
  101. map_mode = TileMap.MODE_ISOMETRIC
  102. "staggered":
  103. map_pos_offset.y -= cell_size.y / 2
  104. match map.staggeraxis:
  105. "x":
  106. map_offset = TileMap.HALF_OFFSET_Y
  107. cell_size.x /= 2.0
  108. if map.staggerindex == "even":
  109. cell_offset.x += 1
  110. map_pos_offset.x -= cell_size.x
  111. "y":
  112. map_offset = TileMap.HALF_OFFSET_X
  113. cell_size.y /= 2.0
  114. if map.staggerindex == "even":
  115. cell_offset.y += 1
  116. map_pos_offset.y -= cell_size.y
  117. "hexagonal":
  118. # Godot maps are always odd and don't have an "even" setting. To
  119. # imitate even staggering we simply start one row/column late and
  120. # adjust the position of the whole map.
  121. match map.staggeraxis:
  122. "x":
  123. map_offset = TileMap.HALF_OFFSET_Y
  124. cell_size.x = int((cell_size.x + map.hexsidelength) / 2)
  125. if map.staggerindex == "even":
  126. cell_offset.x += 1
  127. map_pos_offset.x -= cell_size.x
  128. "y":
  129. map_offset = TileMap.HALF_OFFSET_X
  130. cell_size.y = int((cell_size.y + map.hexsidelength) / 2)
  131. if map.staggerindex == "even":
  132. cell_offset.y += 1
  133. map_pos_offset.y -= cell_size.y
  134.  
  135. var tileset = build_tileset_for_scene(map.tilesets, source_path, options)
  136. if typeof(tileset) != TYPE_OBJECT:
  137. # Error happened
  138. return tileset
  139.  
  140. var root = Node2D.new()
  141. root.set_name(source_path.get_file().get_basename())
  142. if options.save_tiled_properties:
  143. set_tiled_properties_as_meta(root, map)
  144. if options.custom_properties:
  145. set_custom_properties(root, map)
  146.  
  147. var map_data = {
  148. "options": options,
  149. "map_mode": map_mode,
  150. "map_offset": map_offset,
  151. "map_pos_offset": map_pos_offset,
  152. "map_background": map_background,
  153. "cell_size": cell_size,
  154. "cell_offset": cell_offset,
  155. "tileset": tileset,
  156. "source_path": source_path,
  157. "infinite": bool(map.infinite) if "infinite" in map else false
  158. }
  159.  
  160. for layer in map.layers:
  161. err = make_layer(layer, root, root, map_data)
  162. if err != OK:
  163. return err
  164.  
  165. if options.add_background and "backgroundcolor" in map:
  166. var bg_color = str(map.backgroundcolor)
  167. if (!bg_color.is_valid_html_color()):
  168. print_error("Invalid background color format: " + bg_color)
  169. return root
  170.  
  171. map_background = Color(bg_color)
  172.  
  173. var viewport_size = Vector2(ProjectSettings.get("display/window/size/width"), ProjectSettings.get("display/window/size/height"))
  174. var parbg = ParallaxBackground.new()
  175. var parlayer = ParallaxLayer.new()
  176. var colorizer = ColorRect.new()
  177.  
  178. parbg.scroll_ignore_camera_zoom = true
  179. parlayer.motion_mirroring = viewport_size
  180. colorizer.color = map_background
  181. colorizer.rect_size = viewport_size
  182. colorizer.rect_min_size = viewport_size
  183.  
  184. parbg.name = "Background"
  185. root.add_child(parbg)
  186. parbg.owner = root
  187. parlayer.name = "BackgroundLayer"
  188. parbg.add_child(parlayer)
  189. parlayer.owner = root
  190. colorizer.name = "BackgroundColor"
  191. parlayer.add_child(colorizer)
  192. colorizer.owner = root
  193.  
  194. return root
  195.  
  196. # Creates a layer node from the data
  197. # Returns an error code
  198. func make_layer(layer, parent, root, data):
  199. var err = validate_layer(layer)
  200. if err != OK:
  201. return err
  202.  
  203. # Main map data
  204. var map_mode = data.map_mode
  205. var map_offset = data.map_offset
  206. var map_pos_offset = data.map_pos_offset
  207. var cell_size = data.cell_size
  208. var cell_offset = data.cell_offset
  209. var options = data.options
  210. var tileset = data.tileset
  211. var source_path = data.source_path
  212. var infinite = data.infinite
  213.  
  214. var opacity = float(layer.opacity) if "opacity" in layer else 1.0
  215. var visible = bool(layer.visible) if "visible" in layer else true
  216.  
  217. var z_index = 0
  218.  
  219. if "properties" in layer and "z_index" in layer.properties:
  220. z_index = layer.properties.z_index
  221.  
  222. if layer.type == "tilelayer":
  223. var layer_size = Vector2(int(layer.width), int(layer.height))
  224. var tilemap = TileMap.new()
  225. tilemap.set_name(str(layer.name))
  226. tilemap.cell_size = cell_size
  227. tilemap.modulate = Color(1.0, 1.0, 1.0, opacity);
  228. tilemap.visible = visible
  229. tilemap.mode = map_mode
  230. tilemap.cell_half_offset = map_offset
  231. tilemap.format = 1
  232. tilemap.cell_clip_uv = options.uv_clip
  233. tilemap.cell_y_sort = true
  234. tilemap.collision_layer = options.collision_layer
  235. tilemap.z_index = z_index
  236.  
  237. var offset = Vector2()
  238. if "offsetx" in layer:
  239. offset.x = int(layer.offsetx)
  240. if "offsety" in layer:
  241. offset.y = int(layer.offsety)
  242.  
  243. tilemap.position = offset + map_pos_offset
  244. tilemap.tile_set = tileset
  245.  
  246. var chunks = []
  247.  
  248. if infinite:
  249. chunks = layer.chunks
  250. else:
  251. chunks = [layer]
  252.  
  253. for chunk in chunks:
  254. err = validate_chunk(chunk)
  255. if err != OK:
  256. return err
  257.  
  258. var chunk_data = chunk.data
  259.  
  260. if "encoding" in layer and layer.encoding == "base64":
  261. if "compression" in layer:
  262. chunk_data = decompress_layer_data(chunk.data, layer.compression, layer_size)
  263. if typeof(chunk_data) == TYPE_INT:
  264. # Error happened
  265. return chunk_data
  266. else:
  267. chunk_data = read_base64_layer_data(chunk.data)
  268.  
  269. var count = 0
  270. for tile_id in chunk_data:
  271. var int_id = int(str(tile_id)) & 0xFFFFFFFF
  272.  
  273. if int_id == 0:
  274. count += 1
  275. continue
  276.  
  277. var flipped_h = bool(int_id & FLIPPED_HORIZONTALLY_FLAG)
  278. var flipped_v = bool(int_id & FLIPPED_VERTICALLY_FLAG)
  279. var flipped_d = bool(int_id & FLIPPED_DIAGONALLY_FLAG)
  280.  
  281. var gid = int_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  282.  
  283. var cell_x = cell_offset.x + chunk.x + (count % int(chunk.width))
  284. var cell_y = cell_offset.y + chunk.y + int(count / chunk.width)
  285. tilemap.set_cell(cell_x, cell_y, gid, flipped_h, flipped_v, flipped_d)
  286.  
  287. count += 1
  288.  
  289. if options.save_tiled_properties:
  290. set_tiled_properties_as_meta(tilemap, layer)
  291. if options.custom_properties:
  292. set_custom_properties(tilemap, layer)
  293.  
  294. tilemap.set("editor/display_folded", true)
  295. parent.add_child(tilemap)
  296. tilemap.set_owner(root)
  297. elif layer.type == "imagelayer":
  298. var image = null
  299. if layer.image != "":
  300. image = load_image(layer.image, source_path, options)
  301. if typeof(image) != TYPE_OBJECT:
  302. # Error happened
  303. return image
  304.  
  305. var pos = Vector2()
  306. var offset = Vector2()
  307.  
  308. if "x" in layer:
  309. pos.x = float(layer.x)
  310. if "y" in layer:
  311. pos.y = float(layer.y)
  312. if "offsetx" in layer:
  313. offset.x = float(layer.offsetx)
  314. if "offsety" in layer:
  315. offset.y = float(layer.offsety)
  316.  
  317. var sprite = Sprite.new()
  318. sprite.set_name(str(layer.name))
  319. sprite.centered = false
  320. sprite.texture = image
  321. sprite.visible = visible
  322. sprite.modulate = Color(1.0, 1.0, 1.0, opacity)
  323. sprite.z_index = z_index
  324. if options.save_tiled_properties:
  325. set_tiled_properties_as_meta(sprite, layer)
  326. if options.custom_properties:
  327. set_custom_properties(sprite, layer)
  328.  
  329. sprite.set("editor/display_folded", true)
  330. parent.add_child(sprite)
  331. sprite.position = pos + offset
  332. sprite.set_owner(root)
  333. elif layer.type == "objectgroup":
  334. var object_layer = Node2D.new()
  335. if options.save_tiled_properties:
  336. set_tiled_properties_as_meta(object_layer, layer)
  337. if options.custom_properties:
  338. set_custom_properties(object_layer, layer)
  339. object_layer.modulate = Color(1.0, 1.0, 1.0, opacity)
  340. object_layer.visible = visible
  341. object_layer.z_index = z_index
  342. object_layer.set("editor/display_folded", true)
  343. parent.add_child(object_layer)
  344. object_layer.set_owner(root)
  345. if "name" in layer and not str(layer.name).empty():
  346. object_layer.set_name(str(layer.name))
  347.  
  348. if not "draworder" in layer or layer.draworder == "topdown":
  349. layer.objects.sort_custom(self, "object_sorter")
  350.  
  351. for object in layer.objects:
  352. if "template" in object:
  353. var template_file = object["template"]
  354. var template_data_immutable = get_template(remove_filename_from_path(data["source_path"]) + template_file)
  355. if typeof(template_data_immutable) != TYPE_DICTIONARY:
  356. # Error happened
  357. print("Error getting template for object with id " + str(data["id"]))
  358. continue
  359.  
  360. # Overwrite template data with current object data
  361. apply_template(object, template_data_immutable)
  362.  
  363. set_default_obj_params(object)
  364.  
  365. if "point" in object and object.point:
  366. var point = Position2D.new()
  367. if not "x" in object or not "y" in object:
  368. print_error("Missing coordinates for point in object layer.")
  369. continue
  370. point.position = Vector2(float(object.x), float(object.y))
  371. point.visible = bool(object.visible) if "visible" in object else true
  372. object_layer.add_child(point)
  373. point.set_owner(root)
  374. if "name" in object and not str(object.name).empty():
  375. point.set_name(str(object.name))
  376. elif "id" in object and not str(object.id).empty():
  377. point.set_name(str(object.id))
  378. if options.save_tiled_properties:
  379. set_tiled_properties_as_meta(point, object)
  380. if options.custom_properties:
  381. set_custom_properties(point, object)
  382.  
  383. elif not "gid" in object:
  384. # Not a tile object
  385. if "type" in object and object.type == "navigation":
  386. # Can't make navigation objects right now
  387. print_error("Navigation polygons aren't supported in an object layer.")
  388. continue # Non-fatal error
  389. var shape = shape_from_object(object)
  390.  
  391. if typeof(shape) != TYPE_OBJECT:
  392. # Error happened
  393. return shape
  394.  
  395. if "type" in object and object.type == "occluder":
  396. var occluder = LightOccluder2D.new()
  397. var pos = Vector2()
  398. var rot = 0
  399.  
  400. if "x" in object:
  401. pos.x = float(object.x)
  402. if "y" in object:
  403. pos.y = float(object.y)
  404. if "rotation" in object:
  405. rot = float(object.rotation)
  406.  
  407. occluder.visible = bool(object.visible) if "visible" in object else true
  408. occluder.position = pos
  409. occluder.rotation_degrees = rot
  410. occluder.occluder = shape
  411. if "name" in object and not str(object.name).empty():
  412. occluder.set_name(str(object.name))
  413. elif "id" in object and not str(object.id).empty():
  414. occluder.set_name(str(object.id))
  415.  
  416. if options.save_tiled_properties:
  417. set_tiled_properties_as_meta(occluder, object)
  418. if options.custom_properties:
  419. set_custom_properties(occluder, object)
  420.  
  421. object_layer.add_child(occluder)
  422. occluder.set_owner(root)
  423.  
  424. else:
  425. var body = Area2D.new() if object.type == "area" else StaticBody2D.new()
  426.  
  427. var offset = Vector2()
  428. var collision
  429. var pos = Vector2()
  430. var rot = 0
  431.  
  432. if not ("polygon" in object or "polyline" in object):
  433. # Regular shape
  434. collision = CollisionShape2D.new()
  435. collision.shape = shape
  436. if shape is RectangleShape2D:
  437. offset = shape.extents
  438. elif shape is CircleShape2D:
  439. offset = Vector2(shape.radius, shape.radius)
  440. elif shape is CapsuleShape2D:
  441. offset = Vector2(shape.radius, shape.height)
  442. if shape.radius > shape.height:
  443. var temp = shape.radius
  444. shape.radius = shape.height
  445. shape.height = temp
  446. collision.rotation_degrees = 90
  447. shape.height *= 2
  448. collision.position = offset
  449. else:
  450. collision = CollisionPolygon2D.new()
  451. var points = null
  452. if shape is ConcavePolygonShape2D:
  453. points = []
  454. var segments = shape.segments
  455. for i in range(0, segments.size()):
  456. if i % 2 != 0:
  457. continue
  458. points.push_back(segments[i])
  459. collision.build_mode = CollisionPolygon2D.BUILD_SEGMENTS
  460. else:
  461. points = shape.points
  462. collision.build_mode = CollisionPolygon2D.BUILD_SOLIDS
  463. collision.polygon = points
  464.  
  465. collision.one_way_collision = object.type == "one-way"
  466.  
  467. if "x" in object:
  468. pos.x = float(object.x)
  469. if "y" in object:
  470. pos.y = float(object.y)
  471. if "rotation" in object:
  472. rot = float(object.rotation)
  473.  
  474. body.set("editor/display_folded", true)
  475. object_layer.add_child(body)
  476. body.set_owner(root)
  477. body.add_child(collision)
  478. collision.set_owner(root)
  479.  
  480. if options.save_tiled_properties:
  481. set_tiled_properties_as_meta(body, object)
  482. if options.custom_properties:
  483. set_custom_properties(body, object)
  484.  
  485. if "name" in object and not str(object.name).empty():
  486. body.set_name(str(object.name))
  487. elif "id" in object and not str(object.id).empty():
  488. body.set_name(str(object.id))
  489. body.visible = bool(object.visible) if "visible" in object else true
  490. body.position = pos
  491. body.rotation_degrees = rot
  492.  
  493. else: # "gid" in object
  494. var tile_raw_id = int(str(object.gid)) & 0xFFFFFFFF
  495. var tile_id = tile_raw_id & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)
  496.  
  497. var is_tile_object = tileset.tile_get_region(tile_id).get_area() == 0
  498. var collisions = tileset.tile_get_shape_count(tile_id)
  499. var has_collisions = collisions > 0 && object.has("type") && object.type != "sprite"
  500. var sprite = Sprite.new()
  501. var pos = Vector2()
  502. var rot = 0
  503. var scale = Vector2(1, 1)
  504. sprite.texture = tileset.tile_get_texture(tile_id)
  505. var texture_size = sprite.texture.get_size() if sprite.texture != null else Vector2()
  506.  
  507. if not is_tile_object:
  508. sprite.region_enabled = true
  509. sprite.region_rect = tileset.tile_get_region(tile_id)
  510. texture_size = tileset.tile_get_region(tile_id).size
  511.  
  512. sprite.flip_h = bool(tile_raw_id & FLIPPED_HORIZONTALLY_FLAG)
  513. sprite.flip_v = bool(tile_raw_id & FLIPPED_VERTICALLY_FLAG)
  514.  
  515. if "x" in object:
  516. pos.x = float(object.x)
  517. if "y" in object:
  518. pos.y = float(object.y)
  519. if "rotation" in object:
  520. rot = float(object.rotation)
  521. if texture_size != Vector2():
  522. if "width" in object and float(object.width) != texture_size.x:
  523. scale.x = float(object.width) / texture_size.x
  524. if "height" in object and float(object.height) != texture_size.y:
  525. scale.y = float(object.height) / texture_size.y
  526.  
  527. var obj_root = sprite
  528. if has_collisions:
  529. match object.type:
  530. "area": obj_root = Area2D.new()
  531. "kinematic": obj_root = KinematicBody2D.new()
  532. "rigid": obj_root = RigidBody2D.new()
  533. _: obj_root = StaticBody2D.new()
  534.  
  535. object_layer.add_child(obj_root)
  536. obj_root.owner = root
  537.  
  538. obj_root.add_child(sprite)
  539. sprite.owner = root
  540.  
  541. var shapes = tileset.tile_get_shapes(tile_id)
  542. for s in shapes:
  543. var collision_node = CollisionShape2D.new()
  544. collision_node.shape = s.shape
  545.  
  546. collision_node.transform = s.shape_transform
  547. if sprite.flip_h:
  548. collision_node.position.x *= -1
  549. collision_node.position.x -= cell_size.x
  550. collision_node.scale.x *= -1
  551. if sprite.flip_v:
  552. collision_node.scale.y *= -1
  553. collision_node.position.y *= -1
  554. collision_node.position.y -= cell_size.y
  555. obj_root.add_child(collision_node)
  556. collision_node.owner = root
  557.  
  558. if "name" in object and not str(object.name).empty():
  559. obj_root.set_name(str(object.name))
  560. elif "id" in object and not str(object.id).empty():
  561. obj_root.set_name(str(object.id))
  562.  
  563. obj_root.position = pos
  564. obj_root.rotation_degrees = rot
  565. obj_root.visible = bool(object.visible) if "visible" in object else true
  566. obj_root.scale = scale
  567. # Translate from Tiled bottom-left position to Godot top-left
  568. sprite.centered = false
  569. sprite.region_filter_clip = options.uv_clip
  570. sprite.offset = Vector2(0, -texture_size.y)
  571.  
  572. if not has_collisions:
  573. object_layer.add_child(sprite)
  574. sprite.set_owner(root)
  575.  
  576. if options.save_tiled_properties:
  577. set_tiled_properties_as_meta(obj_root, object)
  578. if options.custom_properties:
  579. if options.tile_metadata:
  580. var tile_meta = tileset.get_meta("tile_meta")
  581. if typeof(tile_meta) == TYPE_DICTIONARY and tile_id in tile_meta:
  582. for prop in tile_meta[tile_id]:
  583. obj_root.set_meta(prop, tile_meta[tile_id][prop])
  584. set_custom_properties(obj_root, object)
  585.  
  586. elif layer.type == "group":
  587. var group = Node2D.new()
  588. var pos = Vector2()
  589. if "x" in layer:
  590. pos.x = float(layer.x)
  591. if "y" in layer:
  592. pos.y = float(layer.y)
  593. group.modulate = Color(1.0, 1.0, 1.0, opacity)
  594. group.visible = visible
  595. group.position = pos
  596. group.z_index = z_index
  597.  
  598. if options.save_tiled_properties:
  599. set_tiled_properties_as_meta(group, layer)
  600. if options.custom_properties:
  601. set_custom_properties(group, layer)
  602.  
  603. if "name" in layer and not str(layer.name).empty():
  604. group.set_name(str(layer.name))
  605.  
  606. group.set("editor/display_folded", true)
  607. parent.add_child(group)
  608. group.set_owner(root)
  609.  
  610. for sub_layer in layer.layers:
  611. make_layer(sub_layer, group, root, data)
  612.  
  613. else:
  614. print_error("Unknown layer type ('%s') in '%s'" % [str(layer.type), str(layer.name) if "name" in layer else "[unnamed layer]"])
  615. return ERR_INVALID_DATA
  616.  
  617. return OK
  618.  
  619. func set_default_obj_params(object):
  620. # Set default values for object
  621. for attr in ["width", "height", "rotation", "x", "y"]:
  622. if not attr in object:
  623. object[attr] = 0
  624. if not "type" in object:
  625. object.type = ""
  626. if not "visible" in object:
  627. object.visible = true
  628.  
  629. var flags
  630.  
  631. # Makes a tileset from a array of tilesets data
  632. # Since Godot supports only one TileSet per TileMap, all tilesets from Tiled are combined
  633. func build_tileset_for_scene(tilesets, source_path, options):
  634. var result = TileSet.new()
  635. var err = ERR_INVALID_DATA
  636. var tile_meta = {}
  637.  
  638. for tileset in tilesets:
  639. var ts = tileset
  640. var ts_source_path = source_path
  641. if "source" in ts:
  642. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_integer():
  643. print_error("Missing or invalid firstgid tileset property.")
  644. return ERR_INVALID_DATA
  645.  
  646. ts_source_path = source_path.get_base_dir().plus_file(ts.source)
  647. # Used later for templates
  648. _tileset_path_to_first_gid[ts_source_path] = tileset.firstgid
  649.  
  650. if ts.source.get_extension().to_lower() == "tsx":
  651. var tsx_reader = TiledXMLToDictionary.new()
  652. ts = tsx_reader.read_tsx(ts_source_path)
  653. if typeof(ts) != TYPE_DICTIONARY:
  654. # Error happened
  655. return ts
  656. else: # JSON Tileset
  657. var f = File.new()
  658. err = f.open(ts_source_path, File.READ)
  659. if err != OK:
  660. print_error("Error opening tileset '%s'." % [ts.source])
  661. return err
  662.  
  663. var json_res = JSON.parse(f.get_as_text())
  664. if json_res.error != OK:
  665. print_error("Error parsing tileset '%s' JSON: %s" % [ts.source, json_res.error_string])
  666. return ERR_INVALID_DATA
  667.  
  668. ts = json_res.result
  669. if typeof(ts) != TYPE_DICTIONARY:
  670. print_error("Tileset '%s' is not a dictionary." % [ts.source])
  671. return ERR_INVALID_DATA
  672.  
  673. ts.firstgid = tileset.firstgid
  674.  
  675. err = validate_tileset(ts)
  676. if err != OK:
  677. return err
  678.  
  679. var has_global_image = "image" in ts
  680.  
  681. var spacing = int(ts.spacing) if "spacing" in ts and str(ts.spacing).is_valid_integer() else 0
  682. var margin = int(ts.margin) if "margin" in ts and str(ts.margin).is_valid_integer() else 0
  683. var firstgid = int(ts.firstgid)
  684. var columns = int(ts.columns) if "columns" in ts and str(ts.columns).is_valid_integer() else -1
  685.  
  686. var image = null
  687. var imagesize = Vector2()
  688.  
  689. if has_global_image:
  690. image = load_image(ts.image, ts_source_path, options)
  691. if typeof(image) != TYPE_OBJECT:
  692. # Error happened
  693. return image
  694. imagesize = Vector2(int(ts.imagewidth), int(ts.imageheight))
  695.  
  696. var tilesize = Vector2(int(ts.tilewidth), int(ts.tileheight))
  697.  
  698. var tilecount
  699. if not "tilecount" in ts:
  700. tilecount = make_tilecount(tilesize, imagesize, margin, spacing)
  701. else:
  702. tilecount = int(ts.tilecount)
  703.  
  704.  
  705. var gid = firstgid
  706.  
  707. var x = margin
  708. var y = margin
  709.  
  710. var i = 0
  711. var column = 0
  712.  
  713.  
  714. # Needed to look up textures for animations
  715. var tileRegions = []
  716. while i < tilecount:
  717. var tilepos = Vector2(x, y)
  718. var region = Rect2(tilepos, tilesize)
  719.  
  720. tileRegions.push_back(region)
  721.  
  722. column += 1
  723. i += 1
  724.  
  725. x += int(tilesize.x) + spacing
  726. if (columns > 0 and column >= columns) or x >= int(imagesize.x) - margin or (x + int(tilesize.x)) > int(imagesize.x):
  727. x = margin
  728. y += int(tilesize.y) + spacing
  729. column = 0
  730.  
  731. i = 0
  732.  
  733. while i < tilecount:
  734. var region = tileRegions[i]
  735.  
  736. var rel_id = str(gid - firstgid)
  737.  
  738. result.create_tile(gid)
  739.  
  740. if has_global_image:
  741. if rel_id in ts.tiles && "animation" in ts.tiles[rel_id]:
  742. var animated_tex = AnimatedTexture.new()
  743. animated_tex.frames = ts.tiles[rel_id].animation.size()
  744. animated_tex.fps = 0
  745. var c = 0
  746. # Animated texture wants us to have seperate textures for each frame
  747. # so we have to pull them out of the tileset
  748. var tilesetTexture = image.get_data()
  749. for g in ts.tiles[rel_id].animation:
  750. var frameTex = tilesetTexture.get_rect(tileRegions[(int(g.tileid))])
  751. var newTex = ImageTexture.new()
  752. newTex.create_from_image(frameTex, flags)
  753. animated_tex.set_frame_texture(c, newTex)
  754. animated_tex.set_frame_delay(c, float(g.duration) * 0.001)
  755. c += 1
  756. result.tile_set_texture(gid, animated_tex)
  757. result.tile_set_region(gid, Rect2(Vector2(0, 0), tilesize))
  758. else:
  759. result.tile_set_texture(gid, image)
  760. result.tile_set_region(gid, region)
  761. elif not rel_id in ts.tiles:
  762. gid += 1
  763. continue
  764. else:
  765. if rel_id in ts.tiles && "animation" in ts.tiles[rel_id]:
  766. var animated_tex = AnimatedTexture.new()
  767. animated_tex.frames = ts.tiles[rel_id].animation.size()
  768. animated_tex.fps = 0
  769. var c = 0
  770. #untested
  771. var image_path = ts.tiles[rel_id].image
  772. for g in ts.tiles[rel_id].animation:
  773. animated_tex.set_frame_texture(c, load_image(image_path, ts_source_path, options))
  774. animated_tex.set_frame_delay(c, float(g.duration) * 0.001)
  775. c += 1
  776. result.tile_set_texture(gid, animated_tex)
  777. result.tile_set_region(gid, Rect2(Vector2(0, 0), tilesize))
  778. else:
  779. var image_path = ts.tiles[rel_id].image
  780. image = load_image(image_path, ts_source_path, options)
  781. if typeof(image) != TYPE_OBJECT:
  782. # Error happened
  783. return image
  784. result.tile_set_texture(gid, image)
  785.  
  786. if "tiles" in ts and rel_id in ts.tiles and "objectgroup" in ts.tiles[rel_id] \
  787. and "objects" in ts.tiles[rel_id].objectgroup:
  788. for object in ts.tiles[rel_id].objectgroup.objects:
  789.  
  790. var shape = shape_from_object(object)
  791.  
  792. if typeof(shape) != TYPE_OBJECT:
  793. # Error happened
  794. return shape
  795.  
  796. var offset = Vector2(float(object.x), float(object.y))
  797. if "width" in object and "height" in object:
  798. offset += Vector2(float(object.width) / 2, float(object.height) / 2)
  799.  
  800. if object.type == "navigation":
  801. result.tile_set_navigation_polygon(gid, shape)
  802. result.tile_set_navigation_polygon_offset(gid, offset)
  803. elif object.type == "occluder":
  804. result.tile_set_light_occluder(gid, shape)
  805. result.tile_set_occluder_offset(gid, offset)
  806. else:
  807. result.tile_add_shape(gid, shape, Transform2D(0, offset), object.type == "one-way")
  808.  
  809. if "properties" in ts and "custom_material" in ts.properties:
  810. result.tile_set_material(gid, load(ts.properties.custom_material))
  811.  
  812. if options.custom_properties and options.tile_metadata and "tileproperties" in ts \
  813. and "tilepropertytypes" in ts and rel_id in ts.tileproperties and rel_id in ts.tilepropertytypes:
  814. tile_meta[gid] = get_custom_properties(ts.tileproperties[rel_id], ts.tilepropertytypes[rel_id])
  815. if options.save_tiled_properties and rel_id in ts.tiles:
  816. for property in whitelist_properties:
  817. if property in ts.tiles[rel_id]:
  818. if not gid in tile_meta: tile_meta[gid] = {}
  819. tile_meta[gid][property] = ts.tiles[rel_id][property]
  820.  
  821. # If tile has a custom property called 'name', set the tile's name
  822. if property == "name":
  823. result.tile_set_name(gid, ts.tiles[rel_id].properties.name)
  824.  
  825.  
  826. gid += 1
  827. i += 1
  828.  
  829. if str(ts.name) != "":
  830. result.resource_name = str(ts.name)
  831.  
  832. if options.save_tiled_properties:
  833. set_tiled_properties_as_meta(result, ts)
  834. if options.custom_properties:
  835. if "properties" in ts and "propertytypes" in ts:
  836. set_custom_properties(result, ts)
  837.  
  838. if options.custom_properties and options.tile_metadata:
  839. result.set_meta("tile_meta", tile_meta)
  840.  
  841. return result
  842.  
  843. # Makes a standalone TileSet. Useful for importing TileSets from Tiled
  844. # Returns an error code if fails
  845. func build_tileset(source_path, options):
  846. var set = read_tileset_file(source_path)
  847. if typeof(set) == TYPE_INT:
  848. return set
  849. if typeof(set) != TYPE_DICTIONARY:
  850. return ERR_INVALID_DATA
  851.  
  852. # Just to validate and build correctly using the existing builder
  853. set["firstgid"] = 0
  854.  
  855. return build_tileset_for_scene([set], source_path, options)
  856.  
  857. # Loads an image from a given path
  858. # Returns a Texture
  859. func load_image(rel_path, source_path, options):
  860. flags = options.image_flags if "image_flags" in options else Texture.FLAGS_DEFAULT
  861. var embed = options.embed_internal_images if "embed_internal_images" in options else false
  862.  
  863. var ext = rel_path.get_extension().to_lower()
  864. if ext != "png" and ext != "jpg":
  865. print_error("Unsupported image format: %s. Use PNG or JPG instead." % [ext])
  866. return ERR_FILE_UNRECOGNIZED
  867.  
  868. var total_path = rel_path
  869. if rel_path.is_rel_path():
  870. total_path = ProjectSettings.globalize_path(source_path.get_base_dir()).plus_file(rel_path)
  871. total_path = ProjectSettings.localize_path(total_path)
  872.  
  873. var dir = Directory.new()
  874. if not dir.file_exists(total_path):
  875. print_error("Image not found: %s" % [total_path])
  876. return ERR_FILE_NOT_FOUND
  877.  
  878. if not total_path.begins_with("res://"):
  879. # External images need to be embedded
  880. embed = true
  881.  
  882. var image = null
  883. if embed:
  884. var img = Image.new()
  885. img.load(total_path)
  886. image = ImageTexture.new()
  887. image.create_from_image(img, flags)
  888. else:
  889. image = ResourceLoader.load(total_path, "ImageTexture")
  890. if image != null:
  891. image.set_flags(flags)
  892.  
  893. return image
  894.  
  895. # Reads a file and returns its contents as a dictionary
  896. # Returns an error code if fails
  897. func read_file(path):
  898. if path.get_extension().to_lower() == "tmx":
  899. var tmx_to_dict = TiledXMLToDictionary.new()
  900. var data = tmx_to_dict.read_tmx(path)
  901. if typeof(data) != TYPE_DICTIONARY:
  902. # Error happened
  903. print_error("Error parsing map file '%s'." % [path])
  904. # Return error or result
  905. return data
  906.  
  907. # Not TMX, must be JSON
  908. var file = File.new()
  909. var err = file.open(path, File.READ)
  910. if err != OK:
  911. return err
  912.  
  913. var content = JSON.parse(file.get_as_text())
  914. if content.error != OK:
  915. print_error("Error parsing JSON: " + content.error_string)
  916. return content.error
  917.  
  918. return content.result
  919.  
  920. # Reads a tileset file and return its contents as a dictionary
  921. # Returns an error code if fails
  922. func read_tileset_file(path):
  923. if path.get_extension().to_lower() == "tsx":
  924. var tmx_to_dict = TiledXMLToDictionary.new()
  925. var data = tmx_to_dict.read_tsx(path)
  926. if typeof(data) != TYPE_DICTIONARY:
  927. # Error happened
  928. print_error("Error parsing map file '%s'." % [path])
  929. # Return error or result
  930. return data
  931.  
  932. # Not TSX, must be JSON
  933. var file = File.new()
  934. var err = file.open(path, File.READ)
  935. if err != OK:
  936. return err
  937.  
  938. var content = JSON.parse(file.get_as_text())
  939. if content.error != OK:
  940. print_error("Error parsing JSON: " + content.error_string)
  941. return content.error
  942.  
  943. return content.result
  944.  
  945. # Creates a shape from an object data
  946. # Returns a valid shape depending on the object type (collision/occluder/navigation)
  947. func shape_from_object(object):
  948. var shape = ERR_INVALID_DATA
  949. set_default_obj_params(object)
  950.  
  951. if "polygon" in object or "polyline" in object:
  952. var vertices = PoolVector2Array()
  953.  
  954. if "polygon" in object:
  955. for point in object.polygon:
  956. vertices.push_back(Vector2(float(point.x), float(point.y)))
  957. else:
  958. for point in object.polyline:
  959. vertices.push_back(Vector2(float(point.x), float(point.y)))
  960.  
  961. if object.type == "navigation":
  962. shape = NavigationPolygon.new()
  963. shape.vertices = vertices
  964. shape.add_outline(vertices)
  965. shape.make_polygons_from_outlines()
  966. elif object.type == "occluder":
  967. shape = OccluderPolygon2D.new()
  968. shape.polygon = vertices
  969. shape.closed = "polygon" in object
  970. else:
  971. if is_convex(vertices):
  972. var sorter = PolygonSorter.new()
  973. vertices = sorter.sort_polygon(vertices)
  974. shape = ConvexPolygonShape2D.new()
  975. shape.points = vertices
  976. else:
  977. shape = ConcavePolygonShape2D.new()
  978. var segments = [vertices[0]]
  979. for x in range(1, vertices.size()):
  980. segments.push_back(vertices[x])
  981. segments.push_back(vertices[x])
  982. segments.push_back(vertices[0])
  983. shape.segments = PoolVector2Array(segments)
  984.  
  985. elif "ellipse" in object:
  986. if object.type == "navigation" or object.type == "occluder":
  987. print_error("Ellipse shapes are not supported as navigation or occluder. Use polygon/polyline instead.")
  988. return ERR_INVALID_DATA
  989.  
  990. if not "width" in object or not "height" in object:
  991. print_error("Missing width or height in ellipse shape.")
  992. return ERR_INVALID_DATA
  993.  
  994. var w = abs(float(object.width))
  995. var h = abs(float(object.height))
  996.  
  997. if w == h:
  998. shape = CircleShape2D.new()
  999. shape.radius = w / 2.0
  1000. else:
  1001. # Using a capsule since it's the closest from an ellipse
  1002. shape = CapsuleShape2D.new()
  1003. shape.radius = w / 2.0
  1004. shape.height = h / 2.0
  1005.  
  1006. else: # Rectangle
  1007. if not "width" in object or not "height" in object:
  1008. print_error("Missing width or height in rectangle shape.")
  1009. return ERR_INVALID_DATA
  1010.  
  1011. var size = Vector2(float(object.width), float(object.height))
  1012.  
  1013. if object.type == "navigation" or object.type == "occluder":
  1014. # Those types only accept polygons, so make one from the rectangle
  1015. var vertices = PoolVector2Array([
  1016. Vector2(0, 0),
  1017. Vector2(size.x, 0),
  1018. size,
  1019. Vector2(0, size.y)
  1020. ])
  1021. if object.type == "navigation":
  1022. shape = NavigationPolygon.new()
  1023. shape.vertices = vertices
  1024. shape.add_outline(vertices)
  1025. shape.make_polygons_from_outlines()
  1026. else:
  1027. shape = OccluderPolygon2D.new()
  1028. shape.polygon = vertices
  1029. else:
  1030. shape = RectangleShape2D.new()
  1031. shape.extents = size / 2.0
  1032.  
  1033. return shape
  1034.  
  1035. # Determines if the set of vertices is convex or not
  1036. # Returns a boolean
  1037. func is_convex(vertices):
  1038. var size = vertices.size()
  1039. if size <= 3:
  1040. # Less than 3 verices can't be concave
  1041. return true
  1042.  
  1043. var cp = 0
  1044.  
  1045. for i in range(0, size + 2):
  1046. var p1 = vertices[(i + 0) % size]
  1047. var p2 = vertices[(i + 1) % size]
  1048. var p3 = vertices[(i + 2) % size]
  1049.  
  1050. var prev_cp = cp
  1051. cp = (p2.x - p1.x) * (p3.y - p2.y) - (p2.y - p1.y) * (p3.x - p2.x)
  1052. if i > 0 and sign(cp) != sign(prev_cp):
  1053. return false
  1054.  
  1055. return true
  1056.  
  1057. # Decompress the data of the layer
  1058. # Compression argument is a string, either "gzip" or "zlib"
  1059. func decompress_layer_data(layer_data, compression, map_size):
  1060. if compression != "gzip" and compression != "zlib":
  1061. print_error("Unrecognized compression format: %s" % [compression])
  1062. return ERR_INVALID_DATA
  1063.  
  1064. var compression_type = File.COMPRESSION_DEFLATE if compression == "zlib" else File.COMPRESSION_GZIP
  1065. var expected_size = int(map_size.x) * int(map_size.y) * 4
  1066. var raw_data = Marshalls.base64_to_raw(layer_data).decompress(expected_size, compression_type)
  1067.  
  1068. return decode_layer(raw_data)
  1069.  
  1070. # Reads the layer as a base64 data
  1071. # Returns an array of ints as the decoded layer would be
  1072. func read_base64_layer_data(layer_data):
  1073. var decoded = Marshalls.base64_to_raw(layer_data)
  1074. return decode_layer(decoded)
  1075.  
  1076. # Reads a PoolByteArray and returns the layer array
  1077. # Used for base64 encoded and compressed layers
  1078. func decode_layer(layer_data):
  1079. var result = []
  1080. for i in range(0, layer_data.size(), 4):
  1081. var num = (layer_data[i]) | \
  1082. (layer_data[i + 1] << 8) | \
  1083. (layer_data[i + 2] << 16) | \
  1084. (layer_data[i + 3] << 24)
  1085. result.push_back(num)
  1086. return result
  1087.  
  1088. # Set the custom properties into the metadata of the object
  1089. func set_custom_properties(object, tiled_object):
  1090. if not "properties" in tiled_object or not "propertytypes" in tiled_object:
  1091. return
  1092.  
  1093. var properties = get_custom_properties(tiled_object.properties, tiled_object.propertytypes)
  1094. for property in properties:
  1095. object.set_meta(property, properties[property])
  1096.  
  1097. # Get the custom properties as a dictionary
  1098. # Useful for tile meta, which is not stored directly
  1099. func get_custom_properties(properties, types):
  1100. var result = {}
  1101.  
  1102. for property in properties:
  1103. var value = null
  1104. if str(types[property]).to_lower() == "bool":
  1105. value = bool(properties[property])
  1106. elif str(types[property]).to_lower() == "int":
  1107. value = int(properties[property])
  1108. elif str(types[property]).to_lower() == "float":
  1109. value = float(properties[property])
  1110. elif str(types[property]).to_lower() == "color":
  1111. value = Color(properties[property])
  1112. else:
  1113. value = str(properties[property])
  1114. result[property] = value
  1115. return result
  1116.  
  1117. # Get the available whitelisted properties from the Tiled object
  1118. # And them as metadata in the Godot object
  1119. func set_tiled_properties_as_meta(object, tiled_object):
  1120. for property in whitelist_properties:
  1121. if property in tiled_object:
  1122. object.set_meta(property, tiled_object[property])
  1123.  
  1124. # Custom function to sort objects in an object layer
  1125. # This is done to support the "topdown" draw order, which sorts by 'y' coordinate
  1126. func object_sorter(first, second):
  1127. if first.y == second.y:
  1128. return first.id < second.id
  1129. return first.y < second.y
  1130.  
  1131. # Create the tilecount for the TileSet if not present.
  1132. # Based on the image and tile dimensions.
  1133. func make_tilecount(tilesize, imagesize, margin, spacing):
  1134. var horizontal_tile_size = int(tilesize.x + margin * 2 + spacing)
  1135. var vertical_tile_size = int(tilesize.y + margin * 2 + spacing)
  1136.  
  1137. var horizontal_tile_count = int(imagesize.x) / horizontal_tile_size;
  1138. var vertical_tile_count = int(imagesize.y) / vertical_tile_size;
  1139.  
  1140. return horizontal_tile_count * vertical_tile_count
  1141.  
  1142. # Validates the map dictionary content for missing or invalid keys
  1143. # Returns an error code
  1144. func validate_map(map):
  1145. if not "type" in map or map.type != "map":
  1146. print_error("Missing or invalid type property.")
  1147. return ERR_INVALID_DATA
  1148. elif not "version" in map or int(map.version) != 1:
  1149. print_error("Missing or invalid map version.")
  1150. return ERR_INVALID_DATA
  1151. elif not "tileheight" in map or not str(map.tileheight).is_valid_integer():
  1152. print_error("Missing or invalid tileheight property.")
  1153. return ERR_INVALID_DATA
  1154. elif not "tilewidth" in map or not str(map.tilewidth).is_valid_integer():
  1155. print_error("Missing or invalid tilewidth property.")
  1156. return ERR_INVALID_DATA
  1157. elif not "layers" in map or typeof(map.layers) != TYPE_ARRAY:
  1158. print_error("Missing or invalid layers property.")
  1159. return ERR_INVALID_DATA
  1160. elif not "tilesets" in map or typeof(map.tilesets) != TYPE_ARRAY:
  1161. print_error("Missing or invalid tilesets property.")
  1162. return ERR_INVALID_DATA
  1163. if "orientation" in map and (map.orientation == "staggered" or map.orientation == "hexagonal"):
  1164. if not "staggeraxis" in map:
  1165. print_error("Missing stagger axis property.")
  1166. return ERR_INVALID_DATA
  1167. elif not "staggerindex" in map:
  1168. print_error("Missing stagger axis property.")
  1169. return ERR_INVALID_DATA
  1170. return OK
  1171.  
  1172. # Validates the tileset dictionary content for missing or invalid keys
  1173. # Returns an error code
  1174. func validate_tileset(tileset):
  1175. if not "firstgid" in tileset or not str(tileset.firstgid).is_valid_integer():
  1176. print_error("Missing or invalid firstgid tileset property.")
  1177. return ERR_INVALID_DATA
  1178. elif not "tilewidth" in tileset or not str(tileset.tilewidth).is_valid_integer():
  1179. print_error("Missing or invalid tilewidth tileset property.")
  1180. return ERR_INVALID_DATA
  1181. elif not "tileheight" in tileset or not str(tileset.tileheight).is_valid_integer():
  1182. print_error("Missing or invalid tileheight tileset property.")
  1183. return ERR_INVALID_DATA
  1184. if not "image" in tileset:
  1185. for tile in tileset.tiles:
  1186. if not "image" in tileset.tiles[tile]:
  1187. print_error("Missing or invalid image in tileset property.")
  1188. return ERR_INVALID_DATA
  1189. elif not "imagewidth" in tileset.tiles[tile] or not str(tileset.tiles[tile].imagewidth).is_valid_integer():
  1190. print_error("Missing or invalid imagewidth tileset property 1.")
  1191. return ERR_INVALID_DATA
  1192. elif not "imageheight" in tileset.tiles[tile] or not str(tileset.tiles[tile].imageheight).is_valid_integer():
  1193. print_error("Missing or invalid imageheight tileset property.")
  1194. return ERR_INVALID_DATA
  1195. else:
  1196. if not "imagewidth" in tileset or not str(tileset.imagewidth).is_valid_integer():
  1197. print_error("Missing or invalid imagewidth tileset property 2.")
  1198. return ERR_INVALID_DATA
  1199. elif not "imageheight" in tileset or not str(tileset.imageheight).is_valid_integer():
  1200. print_error("Missing or invalid imageheight tileset property.")
  1201. return ERR_INVALID_DATA
  1202. return OK
  1203.  
  1204. # Validates the layer dictionary content for missing or invalid keys
  1205. # Returns an error code
  1206. func validate_layer(layer):
  1207. if not "type" in layer:
  1208. print_error("Missing or invalid type layer property.")
  1209. return ERR_INVALID_DATA
  1210. elif not "name" in layer:
  1211. print_error("Missing or invalid name layer property.")
  1212. return ERR_INVALID_DATA
  1213. match layer.type:
  1214. "tilelayer":
  1215. if not "height" in layer or not str(layer.height).is_valid_integer():
  1216. print_error("Missing or invalid layer height property.")
  1217. return ERR_INVALID_DATA
  1218. elif not "width" in layer or not str(layer.width).is_valid_integer():
  1219. print_error("Missing or invalid layer width property.")
  1220. return ERR_INVALID_DATA
  1221. elif not "data" in layer:
  1222. if not "chunks" in layer:
  1223. print_error("Missing data or chunks layer properties.")
  1224. return ERR_INVALID_DATA
  1225. elif typeof(layer.chunks) != TYPE_ARRAY:
  1226. print_error("Invalid chunks layer property.")
  1227. return ERR_INVALID_DATA
  1228. elif "encoding" in layer:
  1229. if layer.encoding == "base64" and typeof(layer.data) != TYPE_STRING:
  1230. print_error("Invalid data layer property.")
  1231. return ERR_INVALID_DATA
  1232. if layer.encoding != "base64" and typeof(layer.data) != TYPE_ARRAY:
  1233. print_error("Invalid data layer property.")
  1234. return ERR_INVALID_DATA
  1235. elif typeof(layer.data) != TYPE_ARRAY:
  1236. print_error("Invalid data layer property.")
  1237. return ERR_INVALID_DATA
  1238. if "compression" in layer:
  1239. if layer.compression != "gzip" and layer.compression != "zlib":
  1240. print_error("Invalid compression type.")
  1241. return ERR_INVALID_DATA
  1242. "imagelayer":
  1243. if not "image" in layer or typeof(layer.image) != TYPE_STRING:
  1244. print_error("Missing or invalid image path for layer.")
  1245. return ERR_INVALID_DATA
  1246. "objectgroup":
  1247. if not "objects" in layer or typeof(layer.objects) != TYPE_ARRAY:
  1248. print_error("Missing or invalid objects array for layer.")
  1249. return ERR_INVALID_DATA
  1250. "group":
  1251. if not "layers" in layer or typeof(layer.layers) != TYPE_ARRAY:
  1252. print_error("Missing or invalid layer array for group layer.")
  1253. return ERR_INVALID_DATA
  1254. return OK
  1255.  
  1256. func validate_chunk(chunk):
  1257. if not "data" in chunk:
  1258. print_error("Missing data chunk property.")
  1259. return ERR_INVALID_DATA
  1260. elif not "height" in chunk or not str(chunk.height).is_valid_integer():
  1261. print_error("Missing or invalid height chunk property.")
  1262. return ERR_INVALID_DATA
  1263. elif not "width" in chunk or not str(chunk.width).is_valid_integer():
  1264. print_error("Missing or invalid width chunk property.")
  1265. return ERR_INVALID_DATA
  1266. elif not "x" in chunk or not str(chunk.x).is_valid_integer():
  1267. print_error("Missing or invalid x chunk property.")
  1268. return ERR_INVALID_DATA
  1269. elif not "y" in chunk or not str(chunk.y).is_valid_integer():
  1270. print_error("Missing or invalid y chunk property.")
  1271. return ERR_INVALID_DATA
  1272. return OK
  1273.  
  1274. # Custom function to print error, to centralize the prefix addition
  1275. func print_error(err):
  1276. printerr(error_prefix + err)
  1277.  
  1278. func get_template(path):
  1279. # If this template has not yet been loaded
  1280. if not _loaded_templates.has(path):
  1281. # IS XML
  1282. if path.get_extension().to_lower() == "tx":
  1283. var parser = XMLParser.new()
  1284. var err = parser.open(path)
  1285. if err != OK:
  1286. print_error("Error opening TX file '%s'." % [path])
  1287. return err
  1288. var content = parse_template(parser, path)
  1289. if typeof(content) != TYPE_DICTIONARY:
  1290. # Error happened
  1291. print_error("Error parsing template map file '%s'." % [path])
  1292. return false
  1293. _loaded_templates[path] = content
  1294.  
  1295. # IS JSON
  1296. else:
  1297. var file = File.new()
  1298. var err = file.open(path, File.READ)
  1299. if err != OK:
  1300. return err
  1301.  
  1302. var json_res = JSON.parse(file.get_as_text())
  1303. if json_res.error != OK:
  1304. print_error("Error parsing JSON template map file '%s'." % [path])
  1305. return json_res.error
  1306.  
  1307. var result = json_res.result
  1308. if typeof(result) != TYPE_DICTIONARY:
  1309. print_error("Error parsing JSON template map file '%s'." % [path])
  1310. return ERR_INVALID_DATA
  1311.  
  1312. var object = result.object
  1313. if object.has("gid"):
  1314. if result.has("tileset"):
  1315. var ts_path = remove_filename_from_path(path) + result.tileset.source
  1316. var tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1317. object.gid += tileset_gid_increment
  1318.  
  1319. _loaded_templates[path] = object
  1320.  
  1321. var dict = _loaded_templates[path]
  1322. var dictCopy = {}
  1323. for k in dict:
  1324. dictCopy[k] = dict[k]
  1325.  
  1326. return dictCopy
  1327.  
  1328. func parse_template(parser, path):
  1329. var err = OK
  1330. # Template root node shouldn't have attributes
  1331. var data = {}
  1332. var tileset_gid_increment = 0
  1333. data.id = 0
  1334.  
  1335. err = parser.read()
  1336. while err == OK:
  1337. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  1338. if parser.get_node_name() == "template":
  1339. break
  1340.  
  1341. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  1342. if parser.get_node_name() == "tileset":
  1343. var ts_path = remove_filename_from_path(path) + parser.get_named_attribute_value_safe("source")
  1344. tileset_gid_increment = get_first_gid_from_tileset_path(ts_path) - 1
  1345. data.tileset = ts_path
  1346.  
  1347. if parser.get_node_name() == "object":
  1348. var object = TiledXMLToDictionary.parse_object(parser)
  1349. for k in object:
  1350. data[k] = object[k]
  1351.  
  1352. err = parser.read()
  1353.  
  1354. if data.has("gid"):
  1355. data["gid"] += tileset_gid_increment
  1356.  
  1357. return data
  1358.  
  1359. func get_first_gid_from_tileset_path(path):
  1360. for t in _tileset_path_to_first_gid:
  1361. if is_same_file(path, t):
  1362. return _tileset_path_to_first_gid[t]
  1363.  
  1364. return 0
  1365.  
  1366. static func get_filename_from_path(path):
  1367. var substrings = path.split("/", false)
  1368. var file_name = substrings[substrings.size() - 1]
  1369. return file_name
  1370.  
  1371. static func remove_filename_from_path(path):
  1372. var file_name = get_filename_from_path(path)
  1373. var stringSize = path.length() - file_name.length()
  1374. var file_path = path.substr(0,stringSize)
  1375. return file_path
  1376.  
  1377. static func is_same_file(path1, path2):
  1378. var file1 = File.new()
  1379. var err = file1.open(path1, File.READ)
  1380. if err != OK:
  1381. return err
  1382.  
  1383. var file2 = File.new()
  1384. err = file2.open(path2, File.READ)
  1385. if err != OK:
  1386. return err
  1387.  
  1388. var file1_str = file1.get_as_text()
  1389. var file2_str = file2.get_as_text()
  1390.  
  1391. if file1_str == file2_str:
  1392. return true
  1393.  
  1394. return false
  1395.  
  1396. static func apply_template(object, template_immutable):
  1397. for k in template_immutable:
  1398. # Do not overwrite any object data
  1399. if typeof(template_immutable[k]) == TYPE_DICTIONARY:
  1400. if not object.has(k):
  1401. object[k] = {}
  1402. apply_template(object[k], template_immutable[k])
  1403.  
  1404. elif not object.has(k):
  1405. object[k] = template_immutable[k]