Loader.lua 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. -- Copyright (c) 2011-2012 Casey Baxter
  2. -- See LICENSE file for details
  3. ---------------------------------------------------------------------------------------------------
  4. -- -= Loader =-
  5. ---------------------------------------------------------------------------------------------------
  6. -- Define path so lua knows where to look for files.
  7. local PATH = (...):gsub("[\\/]", ""):match(".+%.") or ''
  8. -- A cache to store tileset images so we don't load them multiple times. Filepaths are keys.
  9. local cache = {} --setmetatable({}, {__mode = "v"})
  10. -- XML parser
  11. local xml = require(PATH .. "xml")
  12. -- Get the map classes
  13. local Map = require(PATH .. "Map")
  14. local TileSet = require(PATH .. "TileSet")
  15. local TileLayer = require(PATH .. "TileLayer")
  16. local Tile = require(PATH .. "Tile")
  17. local Object = require(PATH .. "Object")
  18. local ObjectLayer = require(PATH .. "ObjectLayer")
  19. local Loader = {
  20. path = "", -- The path to tmx files.
  21. filterMin = "nearest", -- The default min filter for image:setFilter()
  22. filterMag = "nearest", -- The default mag filter for image:setFilter()
  23. useSpriteBatch = true, -- The default setting for map.useSpriteBatch
  24. drawObjects = true, -- The default setting for map.drawObjects.
  25. saveDirectory = "Saved Maps", -- The directory to use for Loader.save()
  26. }
  27. local filename -- The name of the tmx file
  28. local fullpath -- The full path to the tmx file minus the name
  29. local colorToBytes = love.math.colorToBytes
  30. local colorFromBytes = love.math.colorFromBytes
  31. local format = string.format
  32. ----------------------------------------------------------------------------------------------------
  33. -- Processes directory up paths
  34. local function directoryUp(path)
  35. while string.find(path, "[^/^\\]+[/\\]%.%.[/\\]") do
  36. path = string.gsub(path, "[^/^\\]+[/\\]%.%.[/\\]", "/", 1)
  37. end
  38. return path
  39. end
  40. local function fileexists(path)
  41. return love.filesystem.getInfo(path)
  42. end
  43. ----------------------------------------------------------------------------------------------------
  44. -- Loads a Map from a tmx file and returns it.
  45. function Loader.load(tofile)
  46. -- Get the raw path
  47. fullpath = directoryUp(Loader.path .. tofile)
  48. -- Get the file name
  49. filename = string.match(fullpath, "[^/^\\]+$")
  50. -- Get the path to the folder containing the file
  51. fullpath = string.gsub(fullpath, "[^/^\\]+$", "")
  52. -- Find out if the file is in the game or save directory
  53. if fileexists(fullpath .. filename) then
  54. elseif fileexists(fullpath .. filename .. ".tmx") then
  55. filename = filename .. ".tmx"
  56. elseif fileexists(Loader.saveDirectory.."/"..filename) then
  57. fullpath = Loader.saveDirectory .. "/"
  58. elseif fileexists(Loader.saveDirectory .. "/" .. filename .. ".tmx") then
  59. fullpath = Loader.saveDirectory .. "/"
  60. filename = filename .. ".tmx"
  61. else
  62. error("Loader.load() Could not find find the file in these locations:\n" ..
  63. "\nGame Directory: " .. fullpath .. filename ..
  64. "\nGame Directory: " .. fullpath .. filename .. ".tmx" ..
  65. "\nSave Directory: " .. Loader.saveDirectory.."/"..filename ..
  66. "\nSave Directory: " .. Loader.saveDirectory.."/"..filename .. ".tmx")
  67. end
  68. -- Read the file and parse it into a table
  69. local t = love.filesystem.read(fullpath .. filename)
  70. t = xml.parse(t)
  71. -- Get the map and expand it
  72. for _, v in pairs(t) do
  73. if v._name == "map" then
  74. return Loader._expandMap(fullpath .. filename, v)
  75. end
  76. end
  77. -- If we made this this far then there wasn't a map tag
  78. error("Loader.load - No map found in file " .. fullpath .. filename)
  79. end
  80. ----------------------------------------------------------------------------------------------------
  81. -- Saves a map to a file.
  82. local xmlHeader = [[<?xml version="1.0" encoding="UTF-8"?>]] .. "\n"
  83. function Loader.save(map, filename)
  84. local mapx = Loader._compactMap(map)
  85. if not fileexists(Loader.saveDirectory) then
  86. love.filesystem.createDirectory(Loader.saveDirectory)
  87. end
  88. love.filesystem.write( Loader.saveDirectory .. "/" .. filename, xmlHeader .. xml.toxml(mapx) )
  89. end
  90. ----------------------------------------------------------------------------------------------------
  91. -- Checks to see if a saved map exists
  92. function Loader.savedMapExists(filename)
  93. if fileexists(Loader.saveDirectory.."/"..filename) then
  94. return true
  95. elseif fileexists(Loader.saveDirectory .. "/" .. filename .. ".tmx") then
  96. return true
  97. end
  98. return false
  99. end
  100. ----------------------------------------------------------------------------------------------------
  101. -- Private
  102. ----------------------------------------------------------------------------------------------------
  103. -- Returns a new image from the filename.
  104. function Loader._newImage(source)
  105. return love.graphics.newImage(source), source:getWidth(), source:getHeight()
  106. end
  107. ----------------------------------------------------------------------------------------------------
  108. -- Checks to see if the table is a valid XML table
  109. function Loader._checkXML(t)
  110. assert(type(t) == "table", "Loader._checkXML - Passed value is not a table")
  111. assert(t ~= Loader, "Loader._checkXML - Passed table is the Loader class. " ..
  112. "You probably used a : instead of a .")
  113. assert(t._name, "Loader._checkXML - Table does not contain _name value")
  114. assert(t._attr, "Loader._checkXML - Table does not contain _attr table")
  115. end
  116. ----------------------------------------------------------------------------------------------------
  117. -- This is used to eliminate naming conflicts. It checks to see if the string is inside the table and
  118. -- continues to rename it until there isn't a conflict.
  119. function Loader._checkName(t, str)
  120. while t[str] do
  121. if string.find(str, "%(%d+%)$") == nil then str = str .. "(1)" end
  122. str = string.gsub(str, "%(%d+%)$", function(a) return "(" ..
  123. tonumber( string.sub(a, string.find(a, "%d+")) ) + 1 .. ")" end)
  124. end
  125. return str
  126. end
  127. ----------------------------------------------------------------------------------------------------
  128. -- Processes a properties table and returns it
  129. function Loader._expandProperties(t)
  130. -- Do some checking
  131. Loader._checkXML(t)
  132. if t._name ~= "properties" then
  133. error("Loader._expandProperties - Passed value is not a properties table")
  134. end
  135. -- Create a properties table and populate it. Will attempt to convert the property to
  136. -- the appropriate type.
  137. local prop = {}
  138. for _,v in pairs(t) do
  139. Loader._checkXML(t)
  140. if v._name == "property" then
  141. if v._attr.value == "true" then
  142. prop[v._attr.name] = true
  143. elseif v._attr.value == "false" then
  144. prop[v._attr.name] = false
  145. else
  146. prop[v._attr.name] = tonumber(v._attr.value) or v._attr.value
  147. end
  148. end
  149. end
  150. -- Return the properties
  151. return prop
  152. end
  153. ----------------------------------------------------------------------------------------------------
  154. -- Process Map data from xml table
  155. function Loader._expandMap(name, t)
  156. -- Do some checking
  157. Loader._checkXML(t)
  158. assert(t._name == "map", "Loader._expandMap - Passed table is not a map")
  159. assert(t._attr.width, t._attr.height, t._attr.tilewidth, t._attr.tileheight,
  160. "Loader._expandMap - Map data is corrupt")
  161. -- We'll use these for temporary storage
  162. local map, tileset, tilelayer, objectlayer, props
  163. local props = {}
  164. -- Get the properties
  165. for _, v in ipairs(t) do
  166. if v._name == "properties" then
  167. props = Loader._expandProperties(v)
  168. end
  169. end
  170. -- Create the map from the settings
  171. local map = Map:new(name, tonumber(t._attr.width),tonumber(t._attr.height),
  172. tonumber(t._attr.tilewidth), tonumber(t._attr.tileheight),
  173. t._attr.orientation, props.atl_directory or fullpath, props)
  174. -- Apply the loader settings if atl_useSpriteBatch or atl_drawObjects was not set
  175. map.useSpriteBatch = map.useSpriteBatch == nil and Loader.useSpriteBatch or map.useSpriteBatch
  176. map.drawObjects = map.drawObjects == nil and Loader.drawObjects or map.drawObjects
  177. -- Now we fill it with the content
  178. for _, v in ipairs(t) do
  179. -- Process TileSet
  180. if v._name == "tileset" then
  181. tileset = Loader._expandTileSet(v, map)
  182. map.tilesets[tileset.name] = tileset
  183. map:updateTiles()
  184. end
  185. -- Process TileLayer
  186. if v._name == "layer" then
  187. tilelayer = Loader._expandTileLayer(v, map)
  188. map.layers[tilelayer.name] = tilelayer
  189. map.layerOrder[#map.layerOrder + 1] = tilelayer
  190. end
  191. -- Process ObjectLayer
  192. if v._name == "objectgroup" then
  193. objectlayer = Loader._expandObjectLayer(v, map)
  194. map.layers[objectlayer.name] = objectlayer
  195. map.layerOrder[#map.layerOrder + 1] = objectlayer
  196. end
  197. -- Process CustomLayer
  198. if v._name == "customlayer" then
  199. map:newCustomLayer(v._attr.name)
  200. for _, v2 in ipairs(v) do
  201. if v2._name == "data" then
  202. map(v._attr.name).data = v2[1]
  203. end
  204. end
  205. end
  206. end
  207. -- Return our map
  208. return map
  209. end
  210. ----------------------------------------------------------------------------------------------------
  211. -- Process TileSet from xml table
  212. function Loader._expandTileSet(t, map)
  213. -- Do some checking
  214. Loader._checkXML(t)
  215. assert(t._name == "tileset", "Loader._expandTileSet - Passed table is not a tileset")
  216. -- Directory of the tileset
  217. local tilesetDir = map._directory
  218. -- If the tileset is an external one then replace it as the tileset. The firstgid is
  219. -- stored in the tileset tag in the original file while the rest of the tileset information
  220. -- is stored in the external file.
  221. if t._attr.source then
  222. local gid = t._attr.firstgid
  223. local path = directoryUp(tilesetDir .. t._attr.source)
  224. t = love.filesystem.read(path)
  225. tilesetDir = string.gsub(path, "[^/^\\]+$", "")
  226. for _,v in pairs(xml.parse(t)) do if v._name == "tileset" then t = v end end
  227. t._attr.firstgid = gid
  228. end
  229. -- Make sure everything loaded correctly
  230. if not t._attr.name or not t._attr.tilewidth or not t._attr.tileheight or not t._attr.firstgid then
  231. error("Loader._expandTileSet - Tileset data is corrupt")
  232. end
  233. -- Temporary storage
  234. local image, imagePath, imageWidth, imageHeight, path, prop, tileSetProperties, trans
  235. local tileProperties = {}
  236. local tileTypes = {}
  237. local offsetX, offsetY = 0, 0
  238. local cached
  239. -- Process elements
  240. for _, v in ipairs(t) do
  241. -- Process image
  242. if v._name == "image" then
  243. imagePath = v._attr.source
  244. path = directoryUp(tilesetDir .. v._attr.source)
  245. cached = cache[path]
  246. -- If the image is in the cache then load it
  247. if cached then
  248. image = cached.image
  249. imageWidth = cached.width
  250. imageHeight = cached.height
  251. trans = cached.trans
  252. -- Else load it and store in the cache
  253. else
  254. image = love.image.newImageData(path)
  255. -- transparent color
  256. trans = v._attr.trans
  257. if trans then
  258. local R, G, B = trans:match("^#?(%x%x)(%x%x)(%x%x)$")
  259. R, G, B = tonumber(R, 16), tonumber(G, 16), tonumber(B, 16)
  260. local rb, gb, bb
  261. image:mapPixel( function(x, y, r, g, b, a)
  262. rb, gb, bb = colorToBytes(r, g, b)
  263. if R == rb and G == gb and B == bb then return r, g, b, 0 end
  264. return r,g,b,a
  265. end)
  266. trans = {colorFromBytes(R, G, B)}
  267. end
  268. -- Set the image information
  269. image, imageWidth, imageHeight = love.graphics.newImage(image), image:getDimensions()
  270. image:setFilter(Loader.filterMin, Loader.filterMag)
  271. -- Cache the created image
  272. cache[path] = {image = image, width = imageWidth, height = imageHeight, trans = trans}
  273. end
  274. end
  275. -- Process tile properties
  276. if v._name == "tile" then
  277. if not v._attr.id then error(v._attr.id) end
  278. local tilegid = t._attr.firstgid + v._attr.id
  279. if v._attr.type then tileTypes[tilegid] = v._attr.type end
  280. for _, v2 in ipairs(v) do
  281. if v2._name == "properties" then
  282. -- Store the property.
  283. tileProperties[tilegid] = Loader._expandProperties(v2)
  284. end
  285. end
  286. end
  287. -- Process tile set properties
  288. if v._name == "properties" then
  289. tileSetProperties = Loader._expandProperties(v)
  290. end
  291. -- Get the tile offset if there is one.
  292. if v._name == "tileoffset" then
  293. offsetX, offsetY = tonumber(v._attr.x or 0), tonumber(v._attr.y or 0)
  294. end
  295. end
  296. -- Make sure that an image was loaded
  297. assert(image, "Loader._expandTileSet - Tileset did not contain an image")
  298. -- Return the TileSet
  299. local tileset = TileSet:new(image, imagePath, Loader._checkName(map.tilesets, t._attr.name),
  300. tonumber(t._attr.tilewidth), tonumber(t._attr.tileheight),
  301. tonumber(imageWidth), tonumber(imageHeight),
  302. tonumber(t._attr.firstgid), tonumber(t._attr.spacing), tonumber(t._attr.margin),
  303. offsetX, offsetY, trans, tileProperties, tileTypes, tileSetProperties)
  304. return tileset
  305. end
  306. ----------------------------------------------------------------------------------------------------
  307. -- Process TileLayer from xml table
  308. function Loader._expandTileLayer(t, map)
  309. -- Do some checking
  310. Loader._checkXML(t)
  311. assert(t._name == "layer", "Loader._expandTileLayer - Passed table is not a tileset")
  312. -- Process elements
  313. local data, properties
  314. for _, v in ipairs(t) do
  315. Loader._checkXML(t)
  316. -- Process data
  317. if v._name == "data" then
  318. data = Loader._expandTileLayerData(v)
  319. end
  320. -- Process TileLayer properties
  321. if v._name == "properties" then
  322. properties = Loader._expandProperties(v)
  323. end
  324. end
  325. -- Create the new layer
  326. local layer = TileLayer:new(map, t._attr.name, t._attr.opacity, properties)
  327. -- Set the visibility
  328. layer.visible = not (t._attr.visible == "0")
  329. -- Populate the tiles and return the layer
  330. layer:_populate(data)
  331. return layer
  332. end
  333. ----------------------------------------------------------------------------------------------------
  334. -- Process TileLayer data from xml table
  335. function Loader._expandTileLayerData(t)
  336. -- Do some checking
  337. Loader._checkXML(t)
  338. assert(t._name == "data", "Loader._expandTileLayerData - Passed table is not TileLayer data")
  339. local data = {}
  340. local encoding = t._attr.encoding
  341. -- If encoded by comma seperated value (csv) then cut each value out and put it into a table.
  342. if encoding == "csv" then
  343. string.gsub(t[1], "[%-%d]+", function(a) data[#data+1] = tonumber(a) or 0 end)
  344. end
  345. -- Base64 encoding. See base64.lua for more details.
  346. if encoding == "base64" then
  347. local compfmt = t._attr.compression
  348. local decoded = love.data.decode("string", "base64", t[1])
  349. -- If a compression method is used
  350. if compfmt == "gzip" or compfmt == "zlib" then
  351. decoded = love.data.decompress("string", compfmt, decoded)
  352. end
  353. local pos = 1
  354. for i = 1, math.floor(#decoded / 4) do
  355. data[i], pos = love.data.unpack("<I4", decoded, pos)
  356. end
  357. end
  358. -- If there is no encoding then the file is probably saved as XML
  359. if encoding == nil then
  360. local i = 1
  361. for k, v in ipairs(t) do
  362. if v._name == "tile" then
  363. data[i] = tonumber(v._attr.gid) or 0
  364. i = i + 1
  365. end
  366. end
  367. end
  368. -- Return the data
  369. return data
  370. end
  371. ----------------------------------------------------------------------------------------------------
  372. -- Process ObjectLayer from xml table
  373. function Loader._expandObjectLayer(t, map)
  374. -- Do some checking
  375. Loader._checkXML(t)
  376. if t._name ~= "objectgroup" then
  377. error("Loader._expandObjectLayer - Passed table is not ObjectLayer data")
  378. end
  379. -- Tiled stores colors in hexidecimal format that looks like "#FFFFFF"
  380. -- We need go convert them into base 10 RGB format
  381. local colorstr = t._attr.color
  382. local color, R, G, B, A
  383. if colorstr then
  384. if #colorstr == 7 then -- #RRGGBB
  385. A, R, G, B = "FF", colorstr:match("^#(%x%x)(%x%x)(%x%x)$")
  386. elseif #colorstr == 9 then -- #AARRGGBB
  387. A, R, G, B = colorstr:match("^#(%x%x)(%x%x)(%x%x)(%x%x)$")
  388. end
  389. if R then
  390. R, G, B, A = tonumber(R, 16), tonumber(G, 16), tonumber(B, 16), tonumber(A, 16)
  391. color = {colorFromBytes(R, G, B, A)}
  392. else
  393. print("unsupported objectgroup color format: " .. colorstr)
  394. end
  395. end
  396. -- Create a new layer
  397. local layer = ObjectLayer:new(map, Loader._checkName(map.layers, t._attr.name), color,
  398. t._attr.opacity)
  399. -- Process elements
  400. local objects = {}
  401. local prop, obj, poly
  402. for _, v in ipairs(t) do
  403. -- Process objects
  404. local obj
  405. if v._name == "object" then
  406. obj = Object:new(layer, v._attr.name, v._attr.type, tonumber(v._attr.x),
  407. tonumber(v._attr.y), tonumber(v._attr.width),
  408. tonumber(v._attr.height), tonumber(v._attr.gid) )
  409. objects[#objects+1] = obj
  410. for _, v2 in ipairs(v) do
  411. -- Process object properties
  412. if v2._name == "properties" then
  413. obj.properties = Loader._expandProperties(v2)
  414. end
  415. -- Process polyline objects
  416. local polylineFunct = function(a)
  417. obj.polyline[#obj.polyline+1] = tonumber(a) or 0
  418. end
  419. if v2._name == "polyline" then
  420. obj.polyline = {}
  421. string.gsub(v2._attr.points, "[%-%d]+", polylineFunct)
  422. end
  423. -- Process polyline objects
  424. local polygonFunct = function(a)
  425. obj.polygon[#obj.polygon+1] = tonumber(a) or 0
  426. end
  427. if v2._name == "polygon" then
  428. obj.polygon = {}
  429. string.gsub(v2._attr.points, "[%-%d]+", polygonFunct)
  430. end
  431. end
  432. obj:updateDrawInfo()
  433. end
  434. -- Process properties
  435. if v._name == "properties" then
  436. prop = Loader._expandProperties(v)
  437. end
  438. end
  439. -- Set the properties and object tables
  440. layer.properties = prop or {}
  441. layer.objects = objects
  442. -- Set the visibility
  443. layer.visible = not (t._attr.visible == "0")
  444. -- Return the layer
  445. return layer
  446. end
  447. ----------------------------------------------------------------------------------------------------
  448. -- Compact a Map
  449. function Loader._compactMap(map)
  450. --map.properties = map.properties or {}
  451. -- Set the ATL properties
  452. map.properties.atl_directory = map._directory
  453. map.properties.atl_useSpritebatch = map.useSpriteBatch
  454. map.properties.atl_viewX = map.viewX
  455. map.properties.atl_viewY = map.viewY
  456. map.properties.atl_viewW = map.viewW
  457. map.properties.atl_viewH = map.viewH
  458. map.properties.atl_viewScaling = map.viewScaling
  459. map.properties.atl_viewPadding = map.viewPadding
  460. map.properties.atl_drawObjects = map.drawObjects
  461. -- <map>
  462. local mapx = {_name = "map", _attr = {
  463. version = "1.0",
  464. orientation = map.orientation,
  465. width = map.width,
  466. height = map.height,
  467. tilewidth = map.tileWidth,
  468. tileheight = map.tileHeight,
  469. }}
  470. -- <tileset>
  471. for k,tileset in pairs(map.tilesets) do
  472. mapx[#mapx+1] = Loader._compactTileSet(tileset)
  473. end
  474. local layer, layerx
  475. for i = 1,#map.layerOrder do
  476. layer = map.layerOrder[i]
  477. -- <layer>
  478. if layer.class == "TileLayer" then
  479. mapx[#mapx+1] = Loader._compactTileLayer(layer)
  480. -- <objectgroup>
  481. elseif layer.class == "ObjectLayer" then
  482. mapx[#mapx+1] = Loader._compactObjectLayer(layer)
  483. -- <custom>
  484. elseif layer.encode then
  485. local layerx = {_name = "customlayer", _attr = {name = layer.name}}
  486. layerx[1] = {_name = "data", _attr = {}}
  487. layerx[1][1] = layer:encode()
  488. mapx[#mapx+1] = layerx
  489. end
  490. end
  491. -- <properties>
  492. mapx[#mapx+1] = Loader._compactProperties(map.properties)
  493. return mapx
  494. end
  495. ----------------------------------------------------------------------------------------------------
  496. -- Compact a TileSet
  497. function Loader._compactTileSet(tileset)
  498. -- <Tileset>
  499. local tilesetx = {_name = "tileset", _attr = {
  500. firstgid = tileset.firstgid,
  501. name = tileset.name,
  502. tilewidth = tileset.tileWidth,
  503. tileheight = tileset.tileHeight,
  504. spacing = tileset.spacing,
  505. margin = tileset.margin
  506. }}
  507. -- <image>
  508. local trans = tileset.trans
  509. if trans then
  510. trans = format("%02x%02x%02x", colorToBytes(trans[1], trans[2], trans[3]))
  511. end
  512. tilesetx[1] = {_name = "image", _attr = {
  513. source = tileset.imagePath,
  514. trans = trans,
  515. }}
  516. -- <tileoffset>
  517. if tileset.offsetX ~= 0 or tileset.offsetY ~= 0 then
  518. tilesetx[2] = {_name = "tileoffset", _attr = {x = tileset.offsetX, y = tileset.offsetY}}
  519. end
  520. -- <properties>
  521. if next(tileset.properties) then
  522. tilesetx[#tilesetx+1] = Loader._compactProperties(tileset.properties)
  523. end
  524. -- <tile>
  525. local hasProp = {}
  526. if next(tileset.tileProperties) then
  527. local tilex, tid
  528. for k, props in pairs(tileset.tileProperties) do
  529. tid = k - tileset.firstgid
  530. tilex = {_name = "tile", _attr = {id = tid}}
  531. tilex[1] = Loader._compactProperties(props)
  532. tilesetx[#tilesetx+1] = tilex
  533. hasProp[tid] = tilex
  534. end
  535. end
  536. if next(tileset.tileTypes) then
  537. local tilex, tid
  538. for k, ttype in pairs(tileset.tileTypes) do
  539. tid = k - tileset.firstgid
  540. tilex = hasProp[tid]
  541. if tilex then
  542. tilex._attr.type = ttype
  543. else
  544. tilex = {_name = "tile", _attr = {id = tid, type = ttype}}
  545. tilesetx[#tilesetx+1] = tilex
  546. end
  547. end
  548. end
  549. return tilesetx
  550. end
  551. ----------------------------------------------------------------------------------------------------
  552. -- Compact a TileLayer
  553. function Loader._compactTileLayer(layer)
  554. -- Set the ATL properties
  555. layer.properties.atl_useSpriteBatch = layer.useSpriteBatch
  556. layer.properties.atl_parallaxX = layer.parallaxX
  557. layer.properties.atl_parallaxY = layer.parallaxY
  558. layer.properties.atl_offsetX = layer.offsetX
  559. layer.properties.atl_offsetY = layer.offsetY
  560. -- <layer>
  561. local layerx = {_name = "layer", _attr = {
  562. name = layer.name,
  563. opacity = layer.opacity,
  564. visible = layer.visible and 1 or 0,
  565. width = layer.map.width,
  566. height = layer.map.height,
  567. }}
  568. -- <data>
  569. local tiles = {}
  570. local flipBits = 2^29
  571. local flipValue
  572. for x, y, tile in layer:rectangle(0, 0, layer.map.width-1, layer.map.height-1, true) do
  573. if tile then
  574. flipValue = layer._flippedTiles(x,y) and layer._flippedTiles(x,y) * flipBits or 0
  575. tiles[#tiles+1] = tile.id + flipValue
  576. else
  577. tiles[#tiles+1] = 0
  578. end
  579. end
  580. layerx[1] = {_name ="data", _attr = {encoding="csv"}, [1] = table.concat(tiles,",")}
  581. -- <properties>
  582. if next(layer.properties) then
  583. layerx[2] = Loader._compactProperties(layer.properties)
  584. end
  585. return layerx
  586. end
  587. ----------------------------------------------------------------------------------------------------
  588. -- Compact an ObjectLayer
  589. function Loader._compactObjectLayer(layer)
  590. local color = layer.color
  591. if color then
  592. color = format("#%02x%02x%02x%02x", colorToBytes(color[4] or 1, color[1], color[2], color[3]))
  593. end
  594. -- <objectgroup>
  595. local layerx = {_name = "objectgroup", _attr = {
  596. name = layer.name,
  597. color = color,
  598. opacity = layer.opacity,
  599. visible = layer.visible and 1 or 0,
  600. }}
  601. -- <object>
  602. --local object
  603. for i = 1,#layer.objects do
  604. --object = layer.objects[i]
  605. layerx[#layerx+1] = Loader._compactObject(layer.objects[i])
  606. end
  607. -- <properties>
  608. if next(layer.properties) then
  609. layerx[#layerx+1] = Loader._compactProperties(layer.properties)
  610. end
  611. return layerx
  612. end
  613. ----------------------------------------------------------------------------------------------------
  614. -- Compact an Object
  615. function Loader._compactObject(object)
  616. -- <object>
  617. local objectx = {_name = "object", _attr = {
  618. name = object.name,
  619. type = object.type,
  620. x = object.x,
  621. y = object.y,
  622. width = object.width,
  623. height = object.height,
  624. gid = object.gid,
  625. visible = object.visible,
  626. }}
  627. -- <polyline>
  628. if object.polyline then
  629. local polylinex = {_name = "polyline", _attr = {}}
  630. local points = {}
  631. for i = 1,#object.polyline,2 do
  632. points[#points+1] = object.polyline[i] .. "," .. object.polyline[i+1]
  633. end
  634. polylinex._attr.points = table.concat(points, " ")
  635. objectx[#objectx+1] = polylinex
  636. end
  637. -- <polygon>
  638. if object.polygon then
  639. local polygonx = {_name = "polygon", _attr = {}}
  640. local points = {}
  641. for i = 1,#object.polygon,2 do
  642. points[#points+1] = object.polygon[i] .. "," .. object.polygon[i+1]
  643. end
  644. polygonx._attr.points = table.concat(points, " ")
  645. objectx[#objectx+1] = polygonx
  646. end
  647. -- <properties>
  648. if next(object.properties) then
  649. objectx[#objectx+1] = Loader._compactProperties(object.properties)
  650. end
  651. return objectx
  652. end
  653. ----------------------------------------------------------------------------------------------------
  654. -- Compact Properties
  655. function Loader._compactProperties(prop)
  656. if not prop then return end
  657. -- <properties>
  658. local propx = {_name = "properties", _attr = {}}
  659. -- <property>
  660. for k,v in pairs(prop) do
  661. propx[#propx+1] = {_name = "property", _attr = {name = k, value = v}}
  662. end
  663. return propx
  664. end
  665. ----------------------------------------------------------------------------------------------------
  666. -- Return the loader
  667. return Loader