init.lua 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617
  1. --- Simple and fast Tiled map loader and renderer.
  2. -- @module sti
  3. -- @author Landon Manning
  4. -- @copyright 2019
  5. -- @license MIT/X11
  6. local STI = {
  7. _LICENSE = "MIT/X11",
  8. _URL = "https://github.com/karai17/Simple-Tiled-Implementation",
  9. _VERSION = "1.2.3.0",
  10. _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.",
  11. cache = {}
  12. }
  13. STI.__index = STI
  14. local love = _G.love
  15. local cwd = (...):gsub('%.init$', '') .. "."
  16. local utils = require(cwd .. "utils")
  17. local ceil = math.ceil
  18. local floor = math.floor
  19. local lg = require(cwd .. "graphics")
  20. local Map = {}
  21. Map.__index = Map
  22. local function new(map, plugins, ox, oy)
  23. local dir = ""
  24. if type(map) == "table" then
  25. map = setmetatable(map, Map)
  26. else
  27. -- Check for valid map type
  28. local ext = map:sub(-4, -1)
  29. assert(ext == ".lua", string.format(
  30. "Invalid file type: %s. File must be of type: lua.",
  31. ext
  32. ))
  33. -- Get directory of map
  34. dir = map:reverse():find("[/\\]") or ""
  35. if dir ~= "" then
  36. dir = map:sub(1, 1 + (#map - dir))
  37. end
  38. -- Load map
  39. map = setmetatable(assert(love.filesystem.load(map))(), Map)
  40. end
  41. map:init(dir, plugins, ox, oy)
  42. return map
  43. end
  44. --- Instance a new map.
  45. -- @param map Path to the map file or the map table itself
  46. -- @param plugins A list of plugins to load
  47. -- @param ox Offset of map on the X axis (in pixels)
  48. -- @param oy Offset of map on the Y axis (in pixels)
  49. -- @return table The loaded Map
  50. function STI.__call(_, map, plugins, ox, oy)
  51. return new(map, plugins, ox, oy)
  52. end
  53. --- Flush image cache.
  54. function STI:flush()
  55. self.cache = {}
  56. end
  57. --- Map object
  58. --- Instance a new map
  59. -- @param path Path to the map file
  60. -- @param plugins A list of plugins to load
  61. -- @param ox Offset of map on the X axis (in pixels)
  62. -- @param oy Offset of map on the Y axis (in pixels)
  63. function Map:init(path, plugins, ox, oy)
  64. if type(plugins) == "table" then
  65. self:loadPlugins(plugins)
  66. end
  67. self:resize()
  68. self.objects = {}
  69. self.tiles = {}
  70. self.tileInstances = {}
  71. self.drawRange = {
  72. sx = 1,
  73. sy = 1,
  74. ex = self.width,
  75. ey = self.height,
  76. }
  77. self.offsetx = ox or 0
  78. self.offsety = oy or 0
  79. self.freeBatchSprites = {}
  80. setmetatable(self.freeBatchSprites, { __mode = 'k' })
  81. -- Set tiles, images
  82. local gid = 1
  83. for i, tileset in ipairs(self.tilesets) do
  84. assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.")
  85. -- Cache images
  86. if lg.isCreated then
  87. local formatted_path = utils.format_path(path .. tileset.image)
  88. if not STI.cache[formatted_path] then
  89. utils.fix_transparent_color(tileset, formatted_path)
  90. utils.cache_image(STI, formatted_path, tileset.image)
  91. else
  92. tileset.image = STI.cache[formatted_path]
  93. end
  94. end
  95. gid = self:setTiles(i, tileset, gid)
  96. end
  97. local layers = {}
  98. for _, layer in ipairs(self.layers) do
  99. self:groupAppendToList(layers, layer)
  100. end
  101. self.layers = layers
  102. -- Set layers
  103. for _, layer in ipairs(self.layers) do
  104. self:setLayer(layer, path)
  105. end
  106. end
  107. --- Layers from the group are added to the list
  108. -- @param layers List of layers
  109. -- @param layer Layer data
  110. function Map:groupAppendToList(layers, layer)
  111. if layer.type == "group" then
  112. for _, groupLayer in pairs(layer.layers) do
  113. groupLayer.name = layer.name .. "." .. groupLayer.name
  114. groupLayer.visible = layer.visible
  115. groupLayer.opacity = layer.opacity * groupLayer.opacity
  116. groupLayer.offsetx = layer.offsetx + groupLayer.offsetx
  117. groupLayer.offsety = layer.offsety + groupLayer.offsety
  118. for key, property in pairs(layer.properties) do
  119. if groupLayer.properties[key] == nil then
  120. groupLayer.properties[key] = property
  121. end
  122. end
  123. self:groupAppendToList(layers, groupLayer)
  124. end
  125. else
  126. table.insert(layers, layer)
  127. end
  128. end
  129. --- Load plugins
  130. -- @param plugins A list of plugins to load
  131. function Map:loadPlugins(plugins)
  132. for _, plugin in ipairs(plugins) do
  133. local pluginModulePath = cwd .. 'plugins.' .. plugin
  134. local ok, pluginModule = pcall(require, pluginModulePath)
  135. if ok then
  136. for k, func in pairs(pluginModule) do
  137. if not self[k] then
  138. self[k] = func
  139. end
  140. end
  141. end
  142. end
  143. end
  144. --- Create Tiles
  145. -- @param index Index of the Tileset
  146. -- @param tileset Tileset data
  147. -- @param gid First Global ID in Tileset
  148. -- @return number Next Tileset's first Global ID
  149. function Map:setTiles(index, tileset, gid)
  150. local quad = lg.newQuad
  151. local imageW = tileset.imagewidth
  152. local imageH = tileset.imageheight
  153. local tileW = tileset.tilewidth
  154. local tileH = tileset.tileheight
  155. local margin = tileset.margin
  156. local spacing = tileset.spacing
  157. local w = utils.get_tiles(imageW, tileW, margin, spacing)
  158. local h = utils.get_tiles(imageH, tileH, margin, spacing)
  159. for y = 1, h do
  160. for x = 1, w do
  161. local id = gid - tileset.firstgid
  162. local quadX = (x - 1) * tileW + margin + (x - 1) * spacing
  163. local quadY = (y - 1) * tileH + margin + (y - 1) * spacing
  164. local type = ""
  165. local properties, terrain, animation, objectGroup
  166. for _, tile in pairs(tileset.tiles) do
  167. if tile.id == id then
  168. properties = tile.properties
  169. animation = tile.animation
  170. objectGroup = tile.objectGroup
  171. type = tile.type
  172. if tile.terrain then
  173. terrain = {}
  174. for i = 1, #tile.terrain do
  175. terrain[i] = tileset.terrains[tile.terrain[i] + 1]
  176. end
  177. end
  178. end
  179. end
  180. local tile = {
  181. id = id,
  182. gid = gid,
  183. tileset = index,
  184. type = type,
  185. quad = quad(
  186. quadX, quadY,
  187. tileW, tileH,
  188. imageW, imageH
  189. ),
  190. properties = properties or {},
  191. terrain = terrain,
  192. animation = animation,
  193. objectGroup = objectGroup,
  194. frame = 1,
  195. time = 0,
  196. width = tileW,
  197. height = tileH,
  198. sx = 1,
  199. sy = 1,
  200. r = 0,
  201. offset = tileset.tileoffset,
  202. }
  203. self.tiles[gid] = tile
  204. gid = gid + 1
  205. end
  206. end
  207. return gid
  208. end
  209. --- Create Layers
  210. -- @param layer Layer data
  211. -- @param path (Optional) Path to an Image Layer's image
  212. function Map:setLayer(layer, path)
  213. if layer.encoding then
  214. if layer.encoding == "base64" then
  215. assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".")
  216. local fd = love.data.decode("string", "base64", layer.data)
  217. if not layer.compression then
  218. layer.data = utils.get_decompressed_data(fd)
  219. else
  220. assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".")
  221. if layer.compression == "zlib" then
  222. local data = love.data.decompress("string", "zlib", fd)
  223. layer.data = utils.get_decompressed_data(data)
  224. end
  225. if layer.compression == "gzip" then
  226. local data = love.data.decompress("string", "gzip", fd)
  227. layer.data = utils.get_decompressed_data(data)
  228. end
  229. end
  230. end
  231. end
  232. layer.x = (layer.x or 0) + layer.offsetx + self.offsetx
  233. layer.y = (layer.y or 0) + layer.offsety + self.offsety
  234. layer.update = function() end
  235. if layer.type == "tilelayer" then
  236. self:setTileData(layer)
  237. self:setSpriteBatches(layer)
  238. layer.draw = function() self:drawTileLayer(layer) end
  239. elseif layer.type == "objectgroup" then
  240. self:setObjectData(layer)
  241. self:setObjectCoordinates(layer)
  242. self:setObjectSpriteBatches(layer)
  243. layer.draw = function() self:drawObjectLayer(layer) end
  244. elseif layer.type == "imagelayer" then
  245. layer.draw = function() self:drawImageLayer(layer) end
  246. if layer.image ~= "" then
  247. local formatted_path = utils.format_path(path .. layer.image)
  248. if not STI.cache[formatted_path] then
  249. utils.cache_image(STI, formatted_path)
  250. end
  251. layer.image = STI.cache[formatted_path]
  252. layer.width = layer.image:getWidth()
  253. layer.height = layer.image:getHeight()
  254. end
  255. end
  256. self.layers[layer.name] = layer
  257. end
  258. --- Add Tiles to Tile Layer
  259. -- @param layer The Tile Layer
  260. function Map:setTileData(layer)
  261. if layer.chunks then
  262. for _, chunk in ipairs(layer.chunks) do
  263. self:setTileData(chunk)
  264. end
  265. return
  266. end
  267. local i = 1
  268. local map = {}
  269. for y = 1, layer.height do
  270. map[y] = {}
  271. for x = 1, layer.width do
  272. local gid = layer.data[i]
  273. -- NOTE: Empty tiles have a GID of 0
  274. if gid > 0 then
  275. map[y][x] = self.tiles[gid] or self:setFlippedGID(gid)
  276. end
  277. i = i + 1
  278. end
  279. end
  280. layer.data = map
  281. end
  282. --- Add Objects to Layer
  283. -- @param layer The Object Layer
  284. function Map:setObjectData(layer)
  285. for _, object in ipairs(layer.objects) do
  286. object.layer = layer
  287. self.objects[object.id] = object
  288. end
  289. end
  290. --- Correct position and orientation of Objects in an Object Layer
  291. -- @param layer The Object Layer
  292. function Map:setObjectCoordinates(layer)
  293. for _, object in ipairs(layer.objects) do
  294. local x = layer.x + object.x
  295. local y = layer.y + object.y
  296. local w = object.width
  297. local h = object.height
  298. local cos = math.cos(math.rad(object.rotation))
  299. local sin = math.sin(math.rad(object.rotation))
  300. if object.shape == "rectangle" and not object.gid then
  301. object.rectangle = {}
  302. local vertices = {
  303. { x=x, y=y },
  304. { x=x + w, y=y },
  305. { x=x + w, y=y + h },
  306. { x=x, y=y + h },
  307. }
  308. for _, vertex in ipairs(vertices) do
  309. vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
  310. table.insert(object.rectangle, { x = vertex.x, y = vertex.y })
  311. end
  312. elseif object.shape == "ellipse" then
  313. object.ellipse = {}
  314. local vertices = utils.convert_ellipse_to_polygon(x, y, w, h)
  315. for _, vertex in ipairs(vertices) do
  316. vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
  317. table.insert(object.ellipse, { x = vertex.x, y = vertex.y })
  318. end
  319. elseif object.shape == "polygon" then
  320. for _, vertex in ipairs(object.polygon) do
  321. vertex.x = vertex.x + x
  322. vertex.y = vertex.y + y
  323. vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
  324. end
  325. elseif object.shape == "polyline" then
  326. for _, vertex in ipairs(object.polyline) do
  327. vertex.x = vertex.x + x
  328. vertex.y = vertex.y + y
  329. vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
  330. end
  331. end
  332. end
  333. end
  334. --- Convert tile location to tile instance location
  335. -- @param layer Tile layer
  336. -- @param tile Tile
  337. -- @param x Tile location on X axis (in tiles)
  338. -- @param y Tile location on Y axis (in tiles)
  339. -- @return number Tile instance location on X axis (in pixels)
  340. -- @return number Tile instance location on Y axis (in pixels)
  341. function Map:getLayerTilePosition(layer, tile, x, y)
  342. local tileW = self.tilewidth
  343. local tileH = self.tileheight
  344. local tileX, tileY
  345. if self.orientation == "orthogonal" then
  346. local tileset = self.tilesets[tile.tileset]
  347. tileX = (x - 1) * tileW + tile.offset.x
  348. tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight
  349. tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH)
  350. elseif self.orientation == "isometric" then
  351. tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2
  352. tileY = (x + y - 2) * (tileH / 2) + tile.offset.y
  353. else
  354. local sideLen = self.hexsidelength or 0
  355. if self.staggeraxis == "y" then
  356. if self.staggerindex == "odd" then
  357. if y % 2 == 0 then
  358. tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
  359. else
  360. tileX = (x - 1) * tileW + tile.offset.x
  361. end
  362. else
  363. if y % 2 == 0 then
  364. tileX = (x - 1) * tileW + tile.offset.x
  365. else
  366. tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
  367. end
  368. end
  369. local rowH = tileH - (tileH - sideLen) / 2
  370. tileY = (y - 1) * rowH + tile.offset.y
  371. else
  372. if self.staggerindex == "odd" then
  373. if x % 2 == 0 then
  374. tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
  375. else
  376. tileY = (y - 1) * tileH + tile.offset.y
  377. end
  378. else
  379. if x % 2 == 0 then
  380. tileY = (y - 1) * tileH + tile.offset.y
  381. else
  382. tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
  383. end
  384. end
  385. local colW = tileW - (tileW - sideLen) / 2
  386. tileX = (x - 1) * colW + tile.offset.x
  387. end
  388. end
  389. return tileX, tileY
  390. end
  391. --- Place new tile instance
  392. -- @param layer Tile layer
  393. -- @param chunk Layer chunk
  394. -- @param tile Tile
  395. -- @param number Tile location on X axis (in tiles)
  396. -- @param number Tile location on Y axis (in tiles)
  397. function Map:addNewLayerTile(layer, chunk, tile, x, y)
  398. local tileset = tile.tileset
  399. local image = self.tilesets[tile.tileset].image
  400. local batches
  401. local size
  402. if chunk then
  403. batches = chunk.batches
  404. size = chunk.width * chunk.height
  405. else
  406. batches = layer.batches
  407. size = layer.width * layer.height
  408. end
  409. batches[tileset] = batches[tileset] or lg.newSpriteBatch(image, size)
  410. local batch = batches[tileset]
  411. local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
  412. local instance = {
  413. layer = layer,
  414. chunk = chunk,
  415. gid = tile.gid,
  416. x = tileX,
  417. y = tileY,
  418. r = tile.r,
  419. oy = 0
  420. }
  421. -- NOTE: STI can run headless so it is not guaranteed that a batch exists.
  422. if batch then
  423. instance.batch = batch
  424. instance.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy)
  425. end
  426. self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
  427. table.insert(self.tileInstances[tile.gid], instance)
  428. end
  429. function Map:set_batches(layer, chunk)
  430. if chunk then
  431. chunk.batches = {}
  432. else
  433. layer.batches = {}
  434. end
  435. if self.orientation == "orthogonal" or self.orientation == "isometric" then
  436. local offsetX = chunk and chunk.x or 0
  437. local offsetY = chunk and chunk.y or 0
  438. local startX = 1
  439. local startY = 1
  440. local endX = chunk and chunk.width or layer.width
  441. local endY = chunk and chunk.height or layer.height
  442. local incrementX = 1
  443. local incrementY = 1
  444. -- Determine order to add tiles to sprite batch
  445. -- Defaults to right-down
  446. if self.renderorder == "right-up" then
  447. startY, endY, incrementY = endY, startY, -1
  448. elseif self.renderorder == "left-down" then
  449. startX, endX, incrementX = endX, startX, -1
  450. elseif self.renderorder == "left-up" then
  451. startX, endX, incrementX = endX, startX, -1
  452. startY, endY, incrementY = endY, startY, -1
  453. end
  454. for y = startY, endY, incrementY do
  455. for x = startX, endX, incrementX do
  456. -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil
  457. local tile
  458. if chunk then
  459. tile = chunk.data[y][x]
  460. else
  461. tile = layer.data[y][x]
  462. end
  463. if tile then
  464. self:addNewLayerTile(layer, chunk, tile, x + offsetX, y + offsetY)
  465. end
  466. end
  467. end
  468. else
  469. if self.staggeraxis == "y" then
  470. for y = 1, (chunk and chunk.height or layer.height) do
  471. for x = 1, (chunk and chunk.width or layer.width) do
  472. -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil
  473. local tile
  474. if chunk then
  475. tile = chunk.data[y][x]
  476. else
  477. tile = layer.data[y][x]
  478. end
  479. if tile then
  480. self:addNewLayerTile(layer, chunk, tile, x, y)
  481. end
  482. end
  483. end
  484. else
  485. local i = 0
  486. local _x
  487. if self.staggerindex == "odd" then
  488. _x = 1
  489. else
  490. _x = 2
  491. end
  492. while i < (chunk and chunk.width * chunk.height or layer.width * layer.height) do
  493. for _y = 1, (chunk and chunk.height or layer.height) + 0.5, 0.5 do
  494. local y = floor(_y)
  495. for x = _x, (chunk and chunk.width or layer.width), 2 do
  496. i = i + 1
  497. -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil
  498. local tile
  499. if chunk then
  500. tile = chunk.data[y][x]
  501. else
  502. tile = layer.data[y][x]
  503. end
  504. if tile then
  505. self:addNewLayerTile(layer, chunk, tile, x, y)
  506. end
  507. end
  508. if _x == 1 then
  509. _x = 2
  510. else
  511. _x = 1
  512. end
  513. end
  514. end
  515. end
  516. end
  517. end
  518. --- Batch Tiles in Tile Layer for improved draw speed
  519. -- @param layer The Tile Layer
  520. function Map:setSpriteBatches(layer)
  521. if layer.chunks then
  522. for _, chunk in ipairs(layer.chunks) do
  523. self:set_batches(layer, chunk)
  524. end
  525. return
  526. end
  527. self:set_batches(layer)
  528. end
  529. --- Batch Tiles in Object Layer for improved draw speed
  530. -- @param layer The Object Layer
  531. function Map:setObjectSpriteBatches(layer)
  532. local newBatch = lg.newSpriteBatch
  533. local batches = {}
  534. if layer.draworder == "topdown" then
  535. table.sort(layer.objects, function(a, b)
  536. return a.y + a.height < b.y + b.height
  537. end)
  538. end
  539. for _, object in ipairs(layer.objects) do
  540. if object.gid then
  541. local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid)
  542. local tileset = tile.tileset
  543. local image = self.tilesets[tileset].image
  544. batches[tileset] = batches[tileset] or newBatch(image)
  545. local sx = object.width / tile.width
  546. local sy = object.height / tile.height
  547. -- Tiled rotates around bottom left corner, where love2D rotates around top left corner
  548. local ox = 0
  549. local oy = tile.height
  550. local batch = batches[tileset]
  551. local tileX = object.x + tile.offset.x
  552. local tileY = object.y + tile.offset.y
  553. local tileR = math.rad(object.rotation)
  554. -- Compensation for scale/rotation shift
  555. if tile.sx == -1 then
  556. tileX = tileX + object.width
  557. if tileR ~= 0 then
  558. tileX = tileX - object.width
  559. ox = ox + tile.width
  560. end
  561. end
  562. if tile.sy == -1 then
  563. tileY = tileY - object.height
  564. if tileR ~= 0 then
  565. tileY = tileY + object.width
  566. oy = oy - tile.width
  567. end
  568. end
  569. local instance = {
  570. id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, ox, oy),
  571. batch = batch,
  572. layer = layer,
  573. gid = tile.gid,
  574. x = tileX,
  575. y = tileY - oy,
  576. r = tileR,
  577. oy = oy
  578. }
  579. self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
  580. table.insert(self.tileInstances[tile.gid], instance)
  581. end
  582. end
  583. layer.batches = batches
  584. end
  585. --- Create a Custom Layer to place userdata in (such as player sprites)
  586. -- @param name Name of Custom Layer
  587. -- @param index Draw order within Layer stack
  588. -- @return table Custom Layer
  589. function Map:addCustomLayer(name, index)
  590. index = index or #self.layers + 1
  591. local layer = {
  592. type = "customlayer",
  593. name = name,
  594. visible = true,
  595. opacity = 1,
  596. properties = {},
  597. }
  598. function layer.draw() end
  599. function layer.update() end
  600. table.insert(self.layers, index, layer)
  601. self.layers[name] = self.layers[index]
  602. return layer
  603. end
  604. --- Convert another Layer into a Custom Layer
  605. -- @param index Index or name of Layer to convert
  606. -- @return table Custom Layer
  607. function Map:convertToCustomLayer(index)
  608. local layer = assert(self.layers[index], "Layer not found: " .. index)
  609. layer.type = "customlayer"
  610. layer.x = nil
  611. layer.y = nil
  612. layer.width = nil
  613. layer.height = nil
  614. layer.encoding = nil
  615. layer.data = nil
  616. layer.chunks = nil
  617. layer.objects = nil
  618. layer.image = nil
  619. function layer.draw() end
  620. function layer.update() end
  621. return layer
  622. end
  623. --- Remove a Layer from the Layer stack
  624. -- @param index Index or name of Layer to remove
  625. function Map:removeLayer(index)
  626. local layer = assert(self.layers[index], "Layer not found: " .. index)
  627. if type(index) == "string" then
  628. for i, l in ipairs(self.layers) do
  629. if l.name == index then
  630. table.remove(self.layers, i)
  631. self.layers[index] = nil
  632. break
  633. end
  634. end
  635. else
  636. local name = self.layers[index].name
  637. table.remove(self.layers, index)
  638. self.layers[name] = nil
  639. end
  640. -- Remove layer batches
  641. if layer.batches then
  642. for _, batch in pairs(layer.batches) do
  643. self.freeBatchSprites[batch] = nil
  644. end
  645. end
  646. -- Remove chunk batches
  647. if layer.chunks then
  648. for _, chunk in ipairs(layer.chunks) do
  649. for _, batch in pairs(chunk.batches) do
  650. self.freeBatchSprites[batch] = nil
  651. end
  652. end
  653. end
  654. -- Remove tile instances
  655. if layer.type == "tilelayer" then
  656. for _, tiles in pairs(self.tileInstances) do
  657. for i = #tiles, 1, -1 do
  658. local tile = tiles[i]
  659. if tile.layer == layer then
  660. table.remove(tiles, i)
  661. end
  662. end
  663. end
  664. end
  665. -- Remove objects
  666. if layer.objects then
  667. for i, object in pairs(self.objects) do
  668. if object.layer == layer then
  669. self.objects[i] = nil
  670. end
  671. end
  672. end
  673. end
  674. --- Animate Tiles and update every Layer
  675. -- @param dt Delta Time
  676. function Map:update(dt)
  677. for _, tile in pairs(self.tiles) do
  678. local update = false
  679. if tile.animation then
  680. tile.time = tile.time + dt * 1000
  681. while tile.time > tonumber(tile.animation[tile.frame].duration) do
  682. update = true
  683. tile.time = tile.time - tonumber(tile.animation[tile.frame].duration)
  684. tile.frame = tile.frame + 1
  685. if tile.frame > #tile.animation then tile.frame = 1 end
  686. end
  687. if update and self.tileInstances[tile.gid] then
  688. for _, j in pairs(self.tileInstances[tile.gid]) do
  689. local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid]
  690. j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy)
  691. end
  692. end
  693. end
  694. end
  695. for _, layer in ipairs(self.layers) do
  696. layer:update(dt)
  697. end
  698. end
  699. --- Draw every Layer
  700. -- @param tx Translate on X
  701. -- @param ty Translate on Y
  702. -- @param sx Scale on X
  703. -- @param sy Scale on Y
  704. function Map:draw(tx, ty, sx, sy)
  705. local current_canvas = lg.getCanvas()
  706. lg.setCanvas(self.canvas)
  707. lg.clear()
  708. -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues
  709. -- Map is translated to correct position so the right section is drawn
  710. lg.push()
  711. lg.origin()
  712. lg.translate(math.floor(tx or 0), math.floor(ty or 0))
  713. for _, layer in ipairs(self.layers) do
  714. if layer.visible and layer.opacity > 0 then
  715. self:drawLayer(layer)
  716. end
  717. end
  718. lg.pop()
  719. -- Draw canvas at 0,0; this fixes scissoring issues
  720. -- Map is scaled to correct scale so the right section is shown
  721. lg.push()
  722. lg.origin()
  723. lg.scale(sx or 1, sy or sx or 1)
  724. lg.setCanvas(current_canvas)
  725. lg.draw(self.canvas)
  726. lg.pop()
  727. end
  728. --- Draw an individual Layer
  729. -- @param layer The Layer to draw
  730. function Map.drawLayer(_, layer)
  731. local r,g,b,a = lg.getColor()
  732. lg.setColor(r, g, b, a * layer.opacity)
  733. layer:draw()
  734. lg.setColor(r,g,b,a)
  735. end
  736. --- Default draw function for Tile Layers
  737. -- @param layer The Tile Layer to draw
  738. function Map:drawTileLayer(layer)
  739. if type(layer) == "string" or type(layer) == "number" then
  740. layer = self.layers[layer]
  741. end
  742. assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer")
  743. -- NOTE: This does not take into account any sort of draw range clipping and will always draw every chunk
  744. if layer.chunks then
  745. for _, chunk in ipairs(layer.chunks) do
  746. for _, batch in pairs(chunk.batches) do
  747. lg.draw(batch, 0, 0)
  748. end
  749. end
  750. return
  751. end
  752. for _, batch in pairs(layer.batches) do
  753. lg.draw(batch, floor(layer.x), floor(layer.y))
  754. end
  755. end
  756. --- Default draw function for Object Layers
  757. -- @param layer The Object Layer to draw
  758. function Map:drawObjectLayer(layer)
  759. if type(layer) == "string" or type(layer) == "number" then
  760. layer = self.layers[layer]
  761. end
  762. assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup")
  763. local line = { 160, 160, 160, 255 * layer.opacity }
  764. local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 }
  765. local r,g,b,a = lg.getColor()
  766. local reset = { r, g, b, a * layer.opacity }
  767. local function sortVertices(obj)
  768. local vertex = {}
  769. for _, v in ipairs(obj) do
  770. table.insert(vertex, v.x)
  771. table.insert(vertex, v.y)
  772. end
  773. return vertex
  774. end
  775. local function drawShape(obj, shape)
  776. local vertex = sortVertices(obj)
  777. if shape == "polyline" then
  778. lg.setColor(line)
  779. lg.line(vertex)
  780. return
  781. elseif shape == "polygon" then
  782. lg.setColor(fill)
  783. if not love.math.isConvex(vertex) then
  784. local triangles = love.math.triangulate(vertex)
  785. for _, triangle in ipairs(triangles) do
  786. lg.polygon("fill", triangle)
  787. end
  788. else
  789. lg.polygon("fill", vertex)
  790. end
  791. else
  792. lg.setColor(fill)
  793. lg.polygon("fill", vertex)
  794. end
  795. lg.setColor(line)
  796. lg.polygon("line", vertex)
  797. end
  798. for _, object in ipairs(layer.objects) do
  799. if object.visible then
  800. if object.shape == "rectangle" and not object.gid then
  801. drawShape(object.rectangle, "rectangle")
  802. elseif object.shape == "ellipse" then
  803. drawShape(object.ellipse, "ellipse")
  804. elseif object.shape == "polygon" then
  805. drawShape(object.polygon, "polygon")
  806. elseif object.shape == "polyline" then
  807. drawShape(object.polyline, "polyline")
  808. elseif object.shape == "point" then
  809. lg.points(object.x, object.y)
  810. end
  811. end
  812. end
  813. lg.setColor(reset)
  814. for _, batch in pairs(layer.batches) do
  815. lg.draw(batch, 0, 0)
  816. end
  817. lg.setColor(r,g,b,a)
  818. end
  819. --- Default draw function for Image Layers
  820. -- @param layer The Image Layer to draw
  821. function Map:drawImageLayer(layer)
  822. if type(layer) == "string" or type(layer) == "number" then
  823. layer = self.layers[layer]
  824. end
  825. assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer")
  826. if layer.image ~= "" then
  827. lg.draw(layer.image, layer.x, layer.y)
  828. end
  829. end
  830. --- Resize the drawable area of the Map
  831. -- @param w The new width of the drawable area (in pixels)
  832. -- @param h The new Height of the drawable area (in pixels)
  833. function Map:resize(w, h)
  834. if lg.isCreated then
  835. w = w or lg.getWidth()
  836. h = h or lg.getHeight()
  837. self.canvas = lg.newCanvas(w, h)
  838. self.canvas:setFilter("nearest", "nearest")
  839. end
  840. end
  841. --- Create flipped or rotated Tiles based on bitop flags
  842. -- @param gid The flagged Global ID
  843. -- @return table Flipped Tile
  844. function Map:setFlippedGID(gid)
  845. local bit31 = 2147483648
  846. local bit30 = 1073741824
  847. local bit29 = 536870912
  848. local flipX = false
  849. local flipY = false
  850. local flipD = false
  851. local realgid = gid
  852. if realgid >= bit31 then
  853. realgid = realgid - bit31
  854. flipX = not flipX
  855. end
  856. if realgid >= bit30 then
  857. realgid = realgid - bit30
  858. flipY = not flipY
  859. end
  860. if realgid >= bit29 then
  861. realgid = realgid - bit29
  862. flipD = not flipD
  863. end
  864. local tile = self.tiles[realgid]
  865. local data = {
  866. id = tile.id,
  867. gid = gid,
  868. tileset = tile.tileset,
  869. frame = tile.frame,
  870. time = tile.time,
  871. width = tile.width,
  872. height = tile.height,
  873. offset = tile.offset,
  874. quad = tile.quad,
  875. properties = tile.properties,
  876. terrain = tile.terrain,
  877. animation = tile.animation,
  878. sx = tile.sx,
  879. sy = tile.sy,
  880. r = tile.r,
  881. }
  882. if flipX then
  883. if flipY and flipD then
  884. data.r = math.rad(-90)
  885. data.sy = -1
  886. elseif flipY then
  887. data.sx = -1
  888. data.sy = -1
  889. elseif flipD then
  890. data.r = math.rad(90)
  891. else
  892. data.sx = -1
  893. end
  894. elseif flipY then
  895. if flipD then
  896. data.r = math.rad(-90)
  897. else
  898. data.sy = -1
  899. end
  900. elseif flipD then
  901. data.r = math.rad(90)
  902. data.sy = -1
  903. end
  904. self.tiles[gid] = data
  905. return self.tiles[gid]
  906. end
  907. --- Get custom properties from Layer
  908. -- @param layer The Layer
  909. -- @return table List of properties
  910. function Map:getLayerProperties(layer)
  911. local l = self.layers[layer]
  912. if not l then
  913. return {}
  914. end
  915. return l.properties
  916. end
  917. --- Get custom properties from Tile
  918. -- @param layer The Layer that the Tile belongs to
  919. -- @param x The X axis location of the Tile (in tiles)
  920. -- @param y The Y axis location of the Tile (in tiles)
  921. -- @return table List of properties
  922. function Map:getTileProperties(layer, x, y)
  923. local tile = self.layers[layer].data[y][x]
  924. if not tile then
  925. return {}
  926. end
  927. return tile.properties
  928. end
  929. --- Get custom properties from Object
  930. -- @param layer The Layer that the Object belongs to
  931. -- @param object The index or name of the Object
  932. -- @return table List of properties
  933. function Map:getObjectProperties(layer, object)
  934. local o = self.layers[layer].objects
  935. if type(object) == "number" then
  936. o = o[object]
  937. else
  938. for _, v in ipairs(o) do
  939. if v.name == object then
  940. o = v
  941. break
  942. end
  943. end
  944. end
  945. if not o then
  946. return {}
  947. end
  948. return o.properties
  949. end
  950. --- Change a tile in a layer to another tile
  951. -- @param layer The Layer that the Tile belongs to
  952. -- @param x The X axis location of the Tile (in tiles)
  953. -- @param y The Y axis location of the Tile (in tiles)
  954. -- @param gid The gid of the new tile
  955. function Map:setLayerTile(layer, x, y, gid)
  956. layer = self.layers[layer]
  957. layer.data[y] = layer.data[y] or {}
  958. local tile = layer.data[y][x]
  959. local instance
  960. if tile then
  961. local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
  962. for _, inst in pairs(self.tileInstances[tile.gid]) do
  963. if inst.x == tileX and inst.y == tileY then
  964. instance = inst
  965. break
  966. end
  967. end
  968. end
  969. if tile == self.tiles[gid] then
  970. return
  971. end
  972. tile = self.tiles[gid]
  973. if instance then
  974. self:swapTile(instance, tile)
  975. else
  976. self:addNewLayerTile(layer, tile, x, y)
  977. end
  978. layer.data[y][x] = tile
  979. end
  980. --- Swap a tile in a spritebatch
  981. -- @param instance The current Instance object we want to replace
  982. -- @param tile The Tile object we want to use
  983. -- @return none
  984. function Map:swapTile(instance, tile)
  985. -- Update sprite batch
  986. if instance.batch then
  987. if tile then
  988. instance.batch:set(
  989. instance.id,
  990. tile.quad,
  991. instance.x,
  992. instance.y,
  993. tile.r,
  994. tile.sx,
  995. tile.sy
  996. )
  997. else
  998. instance.batch:set(
  999. instance.id,
  1000. instance.x,
  1001. instance.y,
  1002. 0,
  1003. 0)
  1004. self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {}
  1005. table.insert(self.freeBatchSprites[instance.batch], instance)
  1006. end
  1007. end
  1008. -- Remove old tile instance
  1009. for i, ins in ipairs(self.tileInstances[instance.gid]) do
  1010. if ins.batch == instance.batch and ins.id == instance.id then
  1011. table.remove(self.tileInstances[instance.gid], i)
  1012. break
  1013. end
  1014. end
  1015. -- Add new tile instance
  1016. if tile then
  1017. self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
  1018. local freeBatchSprites = self.freeBatchSprites[instance.batch]
  1019. local newInstance
  1020. if freeBatchSprites and #freeBatchSprites > 0 then
  1021. newInstance = freeBatchSprites[#freeBatchSprites]
  1022. freeBatchSprites[#freeBatchSprites] = nil
  1023. else
  1024. newInstance = {}
  1025. end
  1026. newInstance.layer = instance.layer
  1027. newInstance.batch = instance.batch
  1028. newInstance.id = instance.id
  1029. newInstance.gid = tile.gid or 0
  1030. newInstance.x = instance.x
  1031. newInstance.y = instance.y
  1032. newInstance.r = tile.r or 0
  1033. newInstance.oy = tile.r ~= 0 and tile.height or 0
  1034. table.insert(self.tileInstances[tile.gid], newInstance)
  1035. end
  1036. end
  1037. --- Convert tile location to pixel location
  1038. -- @param x The X axis location of the point (in tiles)
  1039. -- @param y The Y axis location of the point (in tiles)
  1040. -- @return number The X axis location of the point (in pixels)
  1041. -- @return number The Y axis location of the point (in pixels)
  1042. function Map:convertTileToPixel(x,y)
  1043. if self.orientation == "orthogonal" then
  1044. local tileW = self.tilewidth
  1045. local tileH = self.tileheight
  1046. return
  1047. x * tileW,
  1048. y * tileH
  1049. elseif self.orientation == "isometric" then
  1050. local mapH = self.height
  1051. local tileW = self.tilewidth
  1052. local tileH = self.tileheight
  1053. local offsetX = mapH * tileW / 2
  1054. return
  1055. (x - y) * tileW / 2 + offsetX,
  1056. (x + y) * tileH / 2
  1057. elseif self.orientation == "staggered" or
  1058. self.orientation == "hexagonal" then
  1059. local tileW = self.tilewidth
  1060. local tileH = self.tileheight
  1061. local sideLen = self.hexsidelength or 0
  1062. if self.staggeraxis == "x" then
  1063. return
  1064. x * tileW,
  1065. ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0)
  1066. else
  1067. return
  1068. ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0),
  1069. y * tileH
  1070. end
  1071. end
  1072. end
  1073. --- Convert pixel location to tile location
  1074. -- @param x The X axis location of the point (in pixels)
  1075. -- @param y The Y axis location of the point (in pixels)
  1076. -- @return number The X axis location of the point (in tiles)
  1077. -- @return number The Y axis location of the point (in tiles)
  1078. function Map:convertPixelToTile(x, y)
  1079. if self.orientation == "orthogonal" then
  1080. local tileW = self.tilewidth
  1081. local tileH = self.tileheight
  1082. return
  1083. x / tileW,
  1084. y / tileH
  1085. elseif self.orientation == "isometric" then
  1086. local mapH = self.height
  1087. local tileW = self.tilewidth
  1088. local tileH = self.tileheight
  1089. local offsetX = mapH * tileW / 2
  1090. return
  1091. y / tileH + (x - offsetX) / tileW,
  1092. y / tileH - (x - offsetX) / tileW
  1093. elseif self.orientation == "staggered" then
  1094. local staggerX = self.staggeraxis == "x"
  1095. local even = self.staggerindex == "even"
  1096. local function topLeft(x, y)
  1097. if staggerX then
  1098. if ceil(x) % 2 == 1 and even then
  1099. return x - 1, y
  1100. else
  1101. return x - 1, y - 1
  1102. end
  1103. else
  1104. if ceil(y) % 2 == 1 and even then
  1105. return x, y - 1
  1106. else
  1107. return x - 1, y - 1
  1108. end
  1109. end
  1110. end
  1111. local function topRight(x, y)
  1112. if staggerX then
  1113. if ceil(x) % 2 == 1 and even then
  1114. return x + 1, y
  1115. else
  1116. return x + 1, y - 1
  1117. end
  1118. else
  1119. if ceil(y) % 2 == 1 and even then
  1120. return x + 1, y - 1
  1121. else
  1122. return x, y - 1
  1123. end
  1124. end
  1125. end
  1126. local function bottomLeft(x, y)
  1127. if staggerX then
  1128. if ceil(x) % 2 == 1 and even then
  1129. return x - 1, y + 1
  1130. else
  1131. return x - 1, y
  1132. end
  1133. else
  1134. if ceil(y) % 2 == 1 and even then
  1135. return x, y + 1
  1136. else
  1137. return x - 1, y + 1
  1138. end
  1139. end
  1140. end
  1141. local function bottomRight(x, y)
  1142. if staggerX then
  1143. if ceil(x) % 2 == 1 and even then
  1144. return x + 1, y + 1
  1145. else
  1146. return x + 1, y
  1147. end
  1148. else
  1149. if ceil(y) % 2 == 1 and even then
  1150. return x + 1, y + 1
  1151. else
  1152. return x, y + 1
  1153. end
  1154. end
  1155. end
  1156. local tileW = self.tilewidth
  1157. local tileH = self.tileheight
  1158. if staggerX then
  1159. x = x - (even and tileW / 2 or 0)
  1160. else
  1161. y = y - (even and tileH / 2 or 0)
  1162. end
  1163. local halfH = tileH / 2
  1164. local ratio = tileH / tileW
  1165. local referenceX = ceil(x / tileW)
  1166. local referenceY = ceil(y / tileH)
  1167. local relativeX = x - referenceX * tileW
  1168. local relativeY = y - referenceY * tileH
  1169. if (halfH - relativeX * ratio > relativeY) then
  1170. return topLeft(referenceX, referenceY)
  1171. elseif (-halfH + relativeX * ratio > relativeY) then
  1172. return topRight(referenceX, referenceY)
  1173. elseif (halfH + relativeX * ratio < relativeY) then
  1174. return bottomLeft(referenceX, referenceY)
  1175. elseif (halfH * 3 - relativeX * ratio < relativeY) then
  1176. return bottomRight(referenceX, referenceY)
  1177. end
  1178. return referenceX, referenceY
  1179. elseif self.orientation == "hexagonal" then
  1180. local staggerX = self.staggeraxis == "x"
  1181. local even = self.staggerindex == "even"
  1182. local tileW = self.tilewidth
  1183. local tileH = self.tileheight
  1184. local sideLenX = 0
  1185. local sideLenY = 0
  1186. local colW = tileW / 2
  1187. local rowH = tileH / 2
  1188. if staggerX then
  1189. sideLenX = self.hexsidelength
  1190. x = x - (even and tileW or (tileW - sideLenX) / 2)
  1191. colW = colW - (colW - sideLenX / 2) / 2
  1192. else
  1193. sideLenY = self.hexsidelength
  1194. y = y - (even and tileH or (tileH - sideLenY) / 2)
  1195. rowH = rowH - (rowH - sideLenY / 2) / 2
  1196. end
  1197. local referenceX = ceil(x) / (colW * 2)
  1198. local referenceY = ceil(y) / (rowH * 2)
  1199. -- If in staggered line, then shift reference by 0.5 of other axes
  1200. if staggerX then
  1201. if (floor(referenceX) % 2 == 0) == even then
  1202. referenceY = referenceY - 0.5
  1203. end
  1204. else
  1205. if (floor(referenceY) % 2 == 0) == even then
  1206. referenceX = referenceX - 0.5
  1207. end
  1208. end
  1209. local relativeX = x - referenceX * colW * 2
  1210. local relativeY = y - referenceY * rowH * 2
  1211. local centers
  1212. if staggerX then
  1213. local left = sideLenX / 2
  1214. local centerX = left + colW
  1215. local centerY = tileH / 2
  1216. centers = {
  1217. { x = left, y = centerY },
  1218. { x = centerX, y = centerY - rowH },
  1219. { x = centerX, y = centerY + rowH },
  1220. { x = centerX + colW, y = centerY },
  1221. }
  1222. else
  1223. local top = sideLenY / 2
  1224. local centerX = tileW / 2
  1225. local centerY = top + rowH
  1226. centers = {
  1227. { x = centerX, y = top },
  1228. { x = centerX - colW, y = centerY },
  1229. { x = centerX + colW, y = centerY },
  1230. { x = centerX, y = centerY + rowH }
  1231. }
  1232. end
  1233. local nearest = 0
  1234. local minDist = math.huge
  1235. local function len2(ax, ay)
  1236. return ax * ax + ay * ay
  1237. end
  1238. for i = 1, 4 do
  1239. local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY)
  1240. if dc < minDist then
  1241. minDist = dc
  1242. nearest = i
  1243. end
  1244. end
  1245. local offsetsStaggerX = {
  1246. { x = 1, y = 1 },
  1247. { x = 2, y = 0 },
  1248. { x = 2, y = 1 },
  1249. { x = 3, y = 1 },
  1250. }
  1251. local offsetsStaggerY = {
  1252. { x = 1, y = 1 },
  1253. { x = 0, y = 2 },
  1254. { x = 1, y = 2 },
  1255. { x = 1, y = 3 },
  1256. }
  1257. local offsets = staggerX and offsetsStaggerX or offsetsStaggerY
  1258. return
  1259. referenceX + offsets[nearest].x,
  1260. referenceY + offsets[nearest].y
  1261. end
  1262. end
  1263. --- A list of individual layers indexed both by draw order and name
  1264. -- @table Map.layers
  1265. -- @see TileLayer
  1266. -- @see ObjectLayer
  1267. -- @see ImageLayer
  1268. -- @see CustomLayer
  1269. --- A list of individual tiles indexed by Global ID
  1270. -- @table Map.tiles
  1271. -- @see Tile
  1272. -- @see Map.tileInstances
  1273. --- A list of tile instances indexed by Global ID
  1274. -- @table Map.tileInstances
  1275. -- @see TileInstance
  1276. -- @see Tile
  1277. -- @see Map.tiles
  1278. --- A list of no-longer-used batch sprites, indexed by batch
  1279. --@table Map.freeBatchSprites
  1280. --- A list of individual objects indexed by Global ID
  1281. -- @table Map.objects
  1282. -- @see Object
  1283. --- @table TileLayer
  1284. -- @field name The name of the layer
  1285. -- @field x Position on the X axis (in pixels)
  1286. -- @field y Position on the Y axis (in pixels)
  1287. -- @field width Width of layer (in tiles)
  1288. -- @field height Height of layer (in tiles)
  1289. -- @field visible Toggle if layer is visible or hidden
  1290. -- @field opacity Opacity of layer
  1291. -- @field properties Custom properties
  1292. -- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles)
  1293. -- @field update Update function
  1294. -- @field draw Draw function
  1295. -- @see Map.layers
  1296. -- @see Tile
  1297. --- @table ObjectLayer
  1298. -- @field name The name of the layer
  1299. -- @field x Position on the X axis (in pixels)
  1300. -- @field y Position on the Y axis (in pixels)
  1301. -- @field visible Toggle if layer is visible or hidden
  1302. -- @field opacity Opacity of layer
  1303. -- @field properties Custom properties
  1304. -- @field objects List of objects indexed by draw order
  1305. -- @field update Update function
  1306. -- @field draw Draw function
  1307. -- @see Map.layers
  1308. -- @see Object
  1309. --- @table ImageLayer
  1310. -- @field name The name of the layer
  1311. -- @field x Position on the X axis (in pixels)
  1312. -- @field y Position on the Y axis (in pixels)
  1313. -- @field visible Toggle if layer is visible or hidden
  1314. -- @field opacity Opacity of layer
  1315. -- @field properties Custom properties
  1316. -- @field image Image to be drawn
  1317. -- @field update Update function
  1318. -- @field draw Draw function
  1319. -- @see Map.layers
  1320. --- Custom Layers are used to place userdata such as sprites within the draw order of the map.
  1321. -- @table CustomLayer
  1322. -- @field name The name of the layer
  1323. -- @field x Position on the X axis (in pixels)
  1324. -- @field y Position on the Y axis (in pixels)
  1325. -- @field visible Toggle if layer is visible or hidden
  1326. -- @field opacity Opacity of layer
  1327. -- @field properties Custom properties
  1328. -- @field update Update function
  1329. -- @field draw Draw function
  1330. -- @see Map.layers
  1331. -- @usage
  1332. -- -- Create a Custom Layer
  1333. -- local spriteLayer = map:addCustomLayer("Sprite Layer", 3)
  1334. --
  1335. -- -- Add data to Custom Layer
  1336. -- spriteLayer.sprites = {
  1337. -- player = {
  1338. -- image = lg.newImage("assets/sprites/player.png"),
  1339. -- x = 64,
  1340. -- y = 64,
  1341. -- r = 0,
  1342. -- }
  1343. -- }
  1344. --
  1345. -- -- Update callback for Custom Layer
  1346. -- function spriteLayer:update(dt)
  1347. -- for _, sprite in pairs(self.sprites) do
  1348. -- sprite.r = sprite.r + math.rad(90 * dt)
  1349. -- end
  1350. -- end
  1351. --
  1352. -- -- Draw callback for Custom Layer
  1353. -- function spriteLayer:draw()
  1354. -- for _, sprite in pairs(self.sprites) do
  1355. -- local x = math.floor(sprite.x)
  1356. -- local y = math.floor(sprite.y)
  1357. -- local r = sprite.r
  1358. -- lg.draw(sprite.image, x, y, r)
  1359. -- end
  1360. -- end
  1361. --- @table Tile
  1362. -- @field id Local ID within Tileset
  1363. -- @field gid Global ID
  1364. -- @field tileset Tileset ID
  1365. -- @field quad Quad object
  1366. -- @field properties Custom properties
  1367. -- @field terrain Terrain data
  1368. -- @field animation Animation data
  1369. -- @field frame Current animation frame
  1370. -- @field time Time spent on current animation frame
  1371. -- @field width Width of tile
  1372. -- @field height Height of tile
  1373. -- @field sx Scale value on the X axis
  1374. -- @field sy Scale value on the Y axis
  1375. -- @field r Rotation of tile (in radians)
  1376. -- @field offset Offset drawing position
  1377. -- @field offset.x Offset value on the X axis
  1378. -- @field offset.y Offset value on the Y axis
  1379. -- @see Map.tiles
  1380. --- @table TileInstance
  1381. -- @field batch Spritebatch the Tile Instance belongs to
  1382. -- @field id ID within the spritebatch
  1383. -- @field gid Global ID
  1384. -- @field x Position on the X axis (in pixels)
  1385. -- @field y Position on the Y axis (in pixels)
  1386. -- @see Map.tileInstances
  1387. -- @see Tile
  1388. --- @table Object
  1389. -- @field id Global ID
  1390. -- @field name Name of object (non-unique)
  1391. -- @field shape Shape of object
  1392. -- @field x Position of object on X axis (in pixels)
  1393. -- @field y Position of object on Y axis (in pixels)
  1394. -- @field width Width of object (in pixels)
  1395. -- @field height Heigh tof object (in pixels)
  1396. -- @field rotation Rotation of object (in radians)
  1397. -- @field visible Toggle if object is visible or hidden
  1398. -- @field properties Custom properties
  1399. -- @field ellipse List of verticies of specific shape
  1400. -- @field rectangle List of verticies of specific shape
  1401. -- @field polygon List of verticies of specific shape
  1402. -- @field polyline List of verticies of specific shape
  1403. -- @see Map.objects
  1404. return setmetatable({}, STI)