From 595f877a76a32ef10d260d36922a71d836acb1d9 Mon Sep 17 00:00:00 2001 From: Lobster Date: Wed, 12 Nov 2025 17:55:14 -0500 Subject: [PATCH] Add project files and library for Tiled support --- main/assets/tiles.png | Bin 0 -> 23606 bytes main/libraries/sti/atlas.lua | 159 +++ main/libraries/sti/graphics.lua | 132 ++ main/libraries/sti/init.lua | 1748 ++++++++++++++++++++++++++ main/libraries/sti/plugins/box2d.lua | 323 +++++ main/libraries/sti/plugins/bump.lua | 193 +++ main/libraries/sti/utils.lua | 217 ++++ main/main.lua | 7 +- main/maps/debug_map.tmx | 72 ++ main/maps/sumeriangame.tiled-project | 14 + main/maps/tileset.tsx | 4 + 11 files changed, 2867 insertions(+), 2 deletions(-) create mode 100644 main/assets/tiles.png create mode 100644 main/libraries/sti/atlas.lua create mode 100644 main/libraries/sti/graphics.lua create mode 100644 main/libraries/sti/init.lua create mode 100644 main/libraries/sti/plugins/box2d.lua create mode 100644 main/libraries/sti/plugins/bump.lua create mode 100644 main/libraries/sti/utils.lua create mode 100644 main/maps/debug_map.tmx create mode 100644 main/maps/sumeriangame.tiled-project create mode 100644 main/maps/tileset.tsx diff --git a/main/assets/tiles.png b/main/assets/tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..a7a8237e573ba765db12bc7835cfd78c90232cb7 GIT binary patch literal 23606 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

8)T~ylJohN7|AgV0NmmYc`L^5<%CxA$$ z_yMRhAZ((%M%>0qFN=0S(47YZ0D-`r-vF`OaU#fFQx&i5zX$~DgU81=24c@2uX3cT z1?Y{Z%s#ThPpvUdsRuwMhMSQa5Q+AR>p!WQPiSx4VV3X_n-EkMYyyD5HC6#YRTMWt z)6oeXYcJ2)a8;?XjX)Un!a(4ScR+-VkHAREl5QAJqrdTuZW6INuN8>5s+u0J0HHa5 z$sQ;<*&6Fq0uZEJmxO#h)Bj!~P<*VC(5funbaP+TThc59fRGm_gehqC@nJ?)P42`^ ztD5w6WW~edqN+($k*cPhUqvFsLNADWk_Un~II5Pf8}FVM)kJg1ooa%rM`@}x;1VEM zV(9%jAe3Xznug>zt`Y(39|9G8opM75E6Z02L0r*}6Q=ZDamRuu^LNx(ckTWYI?ao5 z#>T^V2V)Jd3C3ECw;5{$Qu1@S{V-{us)IZb)pc5*UnStp0|ALBjcP(#z-&aj<^Tjq zgzWy}Gjb*o(m+v-v;ZVuKUDVbP+SqRl%3HbIu8W>Wgl^|!-}IYZCS$yI^&bxNcKh{+2n?Faws??4}%khP{iZM6$b%Byi3$#aT zBU~p-fBsp%eqj=ptn|f4*eKK2`~yo}T9OWW)+1tFzo>FUK&R3Z6GN?(VdD|dA;6P> z&dR@%1ikqC*F7Htx;u{pxPVT8kcJy}ep)vig<}y2eHTZ3Cet7Ro~{~k8r9Kk z?C^MR`8tZDqcmE|BZVI%N6aO8^cX3Q?)gY@+{#GEsuYKMA)y(&8RvjxPx@UpQ&4G6 z`juHzlrJOwI+AbJ{G|Q)nDib=zwY@!`rRoJM*97G{2u!I#uYc|{{f}pm_eYu3g-X- N002ovPDHLkV1m{5XW;+< literal 0 HcmV?d00001 diff --git a/main/libraries/sti/atlas.lua b/main/libraries/sti/atlas.lua new file mode 100644 index 0000000..302b332 --- /dev/null +++ b/main/libraries/sti/atlas.lua @@ -0,0 +1,159 @@ +---- Texture atlas complement for the Simple Tiled Implementation +-- @copyright 2022 +-- @author Eduardo Hernández coz.eduardo.hernandez@gmail.com +-- @license MIT/X11 + +local module = {} + +--- Create a texture atlas +-- @param files Array with filenames +-- @param sort If "size" will sort by size, or if "id" will sort by id +-- @param ids Array with ids of each file +-- @param pow2 If true, will force a power of 2 size +function module.Atlas( files, sort, ids, pow2 ) + + local function Node(x, y, w, h) + return {x = x, y = y, w = w, h = h} + end + + local function nextpow2( n ) + local res = 1 + while res <= n do + res = res * 2 + end + return res + end + + local function loadImgs() + local images = {} + for i = 1, #files do + images[i] = {} + --images[i].name = files[i] + if ids then images[i].id = ids[i] end + images[i].img = love.graphics.newImage( files[i] ) + images[i].w = images[i].img:getWidth() + images[i].h = images[i].img:getHeight() + images[i].area = images[i].w * images[i].h + end + if sort == "size" or sort == "id" then + table.sort( images, function( a, b ) return ( a.area > b.area ) end ) + end + return images + end + + --TODO: understand this func + local function add(root, id, w, h) + if root.left or root.right then + if root.left then + local node = add(root.left, id, w, h) + if node then return node end + end + if root.right then + local node = add(root.right, id, w, h) + if node then return node end + end + return nil + end + + if w > root.w or h > root.h then return nil end + + local _w, _h = root.w - w, root.h - h + + if _w <= _h then + root.left = Node(root.x + w, root.y, _w, h) + root.right = Node(root.x, root.y + h, root.w, _h) + else + root.left = Node(root.x, root.y + h, w, _h) + root.right = Node(root.x + w, root.y, _w, root.h) + end + + root.w = w + root.h = h + root.id = id + + return root + end + + local function unmap(root) + if not root then return {} end + + local tree = {} + if root.id then + tree[root.id] = {} + tree[root.id].x, tree[root.id].y = root.x, root.y + end + + local left = unmap(root.left) + local right = unmap(root.right) + + for k, v in pairs(left) do + tree[k] = {} + tree[k].x, tree[k].y = v.x, v.y + end + for k, v in pairs(right) do + tree[k] = {} + tree[k].x, tree[k].y = v.x, v.y + end + + return tree + end + + local function bake() + local images = loadImgs() + + local root = {} + local w, h = images[1].w, images[1].h + + if pow2 then + if w % 1 == 0 then w = nextpow2(w) end + if h % 1 == 0 then h = nextpow2(h) end + end + + repeat + local node + + root = Node(0, 0, w, h) + + for i = 1, #images do + node = add(root, i, images[i].w, images[i].h) + if not node then break end + end + + if not node then + if h <= w then + if pow2 then h = h * 2 else h = h + 1 end + else + if pow2 then w = w * 2 else w = w + 1 end + end + else + break + end + until false + + local limits = love.graphics.getSystemLimits() + if w > limits.texturesize or h > limits.texturesize then + return "Resulting texture is too large for this system" + end + + local coords = unmap(root) + local map = love.graphics.newCanvas(w, h) + love.graphics.setCanvas( map ) +-- love.graphics.clear() + + for i = 1, #images do + love.graphics.draw(images[i].img, coords[i].x, coords[i].y) + if ids then coords[i].id = images[i].id end + end + love.graphics.setCanvas() + + if sort == "ids" then + table.sort( coords, function( a, b ) return ( a.id < b.id ) end ) + end + + return { image = map, coords = coords } + end + + return bake() +end + +return module diff --git a/main/libraries/sti/graphics.lua b/main/libraries/sti/graphics.lua new file mode 100644 index 0000000..6acf8d6 --- /dev/null +++ b/main/libraries/sti/graphics.lua @@ -0,0 +1,132 @@ +local lg = _G.love.graphics +local graphics = { isCreated = lg and true or false } + +function graphics.newSpriteBatch(...) + if graphics.isCreated then + return lg.newSpriteBatch(...) + end +end + +function graphics.newCanvas(...) + if graphics.isCreated then + return lg.newCanvas(...) + end +end + +function graphics.newImage(...) + if graphics.isCreated then + return lg.newImage(...) + end +end + +function graphics.newQuad(...) + if graphics.isCreated then + return lg.newQuad(...) + end +end + +function graphics.getCanvas(...) + if graphics.isCreated then + return lg.getCanvas(...) + end +end + +function graphics.setCanvas(...) + if graphics.isCreated then + return lg.setCanvas(...) + end +end + +function graphics.clear(...) + if graphics.isCreated then + return lg.clear(...) + end +end + +function graphics.push(...) + if graphics.isCreated then + return lg.push(...) + end +end + +function graphics.origin(...) + if graphics.isCreated then + return lg.origin(...) + end +end + +function graphics.scale(...) + if graphics.isCreated then + return lg.scale(...) + end +end + +function graphics.translate(...) + if graphics.isCreated then + return lg.translate(...) + end +end + +function graphics.pop(...) + if graphics.isCreated then + return lg.pop(...) + end +end + +function graphics.draw(...) + if graphics.isCreated then + return lg.draw(...) + end +end + +function graphics.rectangle(...) + if graphics.isCreated then + return lg.rectangle(...) + end +end + +function graphics.getColor(...) + if graphics.isCreated then + return lg.getColor(...) + end +end + +function graphics.setColor(...) + if graphics.isCreated then + return lg.setColor(...) + end +end + +function graphics.line(...) + if graphics.isCreated then + return lg.line(...) + end +end + +function graphics.polygon(...) + if graphics.isCreated then + return lg.polygon(...) + end +end + +function graphics.points(...) + if graphics.isCreated then + return lg.points(...) + end +end + +function graphics.getWidth() + if graphics.isCreated then + return lg.getWidth() + end + return 0 +end + +function graphics.getHeight() + if graphics.isCreated then + return lg.getHeight() + end + return 0 +end + +return graphics diff --git a/main/libraries/sti/init.lua b/main/libraries/sti/init.lua new file mode 100644 index 0000000..646a224 --- /dev/null +++ b/main/libraries/sti/init.lua @@ -0,0 +1,1748 @@ +--- Simple and fast Tiled map loader and renderer. +-- @module sti +-- @author Landon Manning +-- @copyright 2019 +-- @license MIT/X11 + +local STI = { + _LICENSE = "MIT/X11", + _URL = "https://github.com/karai17/Simple-Tiled-Implementation", + _VERSION = "1.2.3.0", + _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.", + cache = {} +} +STI.__index = STI + +local love = _G.love +local cwd = (...):gsub('%.init$', '') .. "." +local utils = require(cwd .. "utils") +local ceil = math.ceil +local floor = math.floor +local lg = require(cwd .. "graphics") +local atlas = require(cwd .. "atlas") +local Map = {} +Map.__index = Map + +local function new(map, plugins, ox, oy) + local dir = "" + + if type(map) == "table" then + map = setmetatable(map, Map) + else + -- Check for valid map type + local ext = map:sub(-4, -1) + assert(ext == ".lua", string.format( + "Invalid file type: %s. File must be of type: lua.", + ext + )) + + -- Get directory of map + dir = map:reverse():find("[/\\]") or "" + if dir ~= "" then + dir = map:sub(1, 1 + (#map - dir)) + end + + -- Load map + map = setmetatable(assert(love.filesystem.load(map))(), Map) + end + + map:init(dir, plugins, ox, oy) + + return map +end + +--- Instance a new map. +-- @param map Path to the map file or the map table itself +-- @param plugins A list of plugins to load +-- @param ox Offset of map on the X axis (in pixels) +-- @param oy Offset of map on the Y axis (in pixels) +-- @return table The loaded Map +function STI.__call(_, map, plugins, ox, oy) + return new(map, plugins, ox, oy) +end + +--- Flush image cache. +function STI:flush() + self.cache = {} +end + +--- Map object + +--- Instance a new map +-- @param path Path to the map file +-- @param plugins A list of plugins to load +-- @param ox Offset of map on the X axis (in pixels) +-- @param oy Offset of map on the Y axis (in pixels) +function Map:init(path, plugins, ox, oy) + if type(plugins) == "table" then + self:loadPlugins(plugins) + end + + self:resize() + self.objects = {} + self.tiles = {} + self.tileInstances = {} + self.offsetx = ox or 0 + self.offsety = oy or 0 + + self.freeBatchSprites = {} + setmetatable(self.freeBatchSprites, { __mode = 'k' }) + + -- Set tiles, images + local gid = 1 + for i, tileset in ipairs(self.tilesets) do + assert(not tileset.filename, "STI does not support external Tilesets.\nYou need to embed all Tilesets.") + + if tileset.image then + -- Cache images + if lg.isCreated then + local formatted_path = utils.format_path(path .. tileset.image) + + if not STI.cache[formatted_path] then + utils.fix_transparent_color(tileset, formatted_path) + utils.cache_image(STI, formatted_path, tileset.image) + else + tileset.image = STI.cache[formatted_path] + end + end + + gid = self:setTiles(i, tileset, gid) + elseif tileset.tilecount > 0 then + -- Build atlas for image collection + local files, ids = {}, {} + for j = 1, #tileset.tiles do + files[ j ] = utils.format_path(path .. tileset.tiles[j].image) + ids[ j ] = tileset.tiles[j].id + end + + local map = atlas.Atlas( files, "ids", ids ) + + if lg.isCreated then + local formatted_path = utils.format_path(path .. tileset.name) + + if not STI.cache[formatted_path] then + -- No need to fix transparency color for collections + utils.cache_image(STI, formatted_path, map.image) + tileset.image = map.image + else + tileset.image = STI.cache[formatted_path] + end + end + + gid = self:setAtlasTiles(i, tileset, map.coords, gid) + end + end + + local layers = {} + for _, layer in ipairs(self.layers) do + self:groupAppendToList(layers, layer) + end + self.layers = layers + + -- Set layers + for _, layer in ipairs(self.layers) do + self:setLayer(layer, path) + end +end + +--- Layers from the group are added to the list +-- @param layers List of layers +-- @param layer Layer data +function Map:groupAppendToList(layers, layer) + if layer.type == "group" then + for _, groupLayer in pairs(layer.layers) do + groupLayer.name = layer.name .. "." .. groupLayer.name + groupLayer.visible = layer.visible + groupLayer.opacity = layer.opacity * groupLayer.opacity + groupLayer.offsetx = layer.offsetx + groupLayer.offsetx + groupLayer.offsety = layer.offsety + groupLayer.offsety + + for key, property in pairs(layer.properties) do + if groupLayer.properties[key] == nil then + groupLayer.properties[key] = property + end + end + + self:groupAppendToList(layers, groupLayer) + end + else + table.insert(layers, layer) + end +end + +--- Load plugins +-- @param plugins A list of plugins to load +function Map:loadPlugins(plugins) + for _, plugin in ipairs(plugins) do + local pluginModulePath = cwd .. 'plugins.' .. plugin + local ok, pluginModule = pcall(require, pluginModulePath) + if ok then + for k, func in pairs(pluginModule) do + if not self[k] then + self[k] = func + end + end + end + end +end + +--- Create Tiles based on a single tileset image +-- @param index Index of the Tileset +-- @param tileset Tileset data +-- @param gid First Global ID in Tileset +-- @return number Next Tileset's first Global ID +function Map:setTiles(index, tileset, gid) + local quad = lg.newQuad + local imageW = tileset.imagewidth + local imageH = tileset.imageheight + local tileW = tileset.tilewidth + local tileH = tileset.tileheight + local margin = tileset.margin + local spacing = tileset.spacing + local w = utils.get_tiles(imageW, tileW, margin, spacing) + local h = utils.get_tiles(imageH, tileH, margin, spacing) + + for y = 1, h do + for x = 1, w do + local id = gid - tileset.firstgid + local quadX = (x - 1) * tileW + margin + (x - 1) * spacing + local quadY = (y - 1) * tileH + margin + (y - 1) * spacing + local type = "" + local properties, terrain, animation, objectGroup + + for _, tile in pairs(tileset.tiles) do + if tile.id == id then + properties = tile.properties + animation = tile.animation + objectGroup = tile.objectGroup + type = tile.type + + if tile.terrain then + terrain = {} + + for i = 1, #tile.terrain do + terrain[i] = tileset.terrains[tile.terrain[i] + 1] + end + end + end + end + + local tile = { + id = id, + gid = gid, + tileset = index, + type = type, + quad = quad( + quadX, quadY, + tileW, tileH, + imageW, imageH + ), + properties = properties or {}, + terrain = terrain, + animation = animation, + objectGroup = objectGroup, + frame = 1, + time = 0, + width = tileW, + height = tileH, + sx = 1, + sy = 1, + r = 0, + offset = tileset.tileoffset, + } + + self.tiles[gid] = tile + gid = gid + 1 + end + end + + return gid +end + +--- Create Tiles based on a texture atlas +-- @param index Index of the Tileset +-- @param tileset Tileset data +-- @param coords Tile XY location in the atlas +-- @param gid First Global ID in Tileset +-- @return number Next Tileset's first Global ID +function Map:setAtlasTiles(index, tileset, coords, gid) + local quad = lg.newQuad + local imageW = tileset.image:getWidth() + local imageH = tileset.image:getHeight() + + local firstgid = tileset.firstgid + for i = 1, #tileset.tiles do + local tile = tileset.tiles[i] + if tile.terrain then + terrain = {} + + for j = 1, #tile.terrain do + terrain[j] = tileset.terrains[tile.terrain[j] + 1] + end + end + + local tile = { + id = tile.id, + gid = firstgid + tile.id, + tileset = index, + class = tile.class, + quad = quad( + coords[i].x, coords[i].y, + tile.width, tile.height, + imageW, imageH + ), + properties = tile.properties or {}, + terrain = terrain, + animation = tile.animation, + objectGroup = tile.objectGroup, + frame = 1, + time = 0, + width = tile.width, + height = tile.height, + sx = 1, + sy = 1, + r = 0, + offset = tileset.tileoffset, + } + + -- Be aware that in collections self.tiles can be a sparse array + self.tiles[tile.gid] = tile + end + + return gid + #tileset.tiles +end + +--- Create Layers +-- @param layer Layer data +-- @param path (Optional) Path to an Image Layer's image +function Map:setLayer(layer, path) + if layer.encoding then + if layer.encoding == "base64" then + assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".") + local fd = love.data.decode("string", "base64", layer.data) + + if not layer.compression then + layer.data = utils.get_decompressed_data(fd) + else + assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".") + + if layer.compression == "zlib" then + local data = love.data.decompress("string", "zlib", fd) + layer.data = utils.get_decompressed_data(data) + end + + if layer.compression == "gzip" then + local data = love.data.decompress("string", "gzip", fd) + layer.data = utils.get_decompressed_data(data) + end + end + end + end + + layer.x = (layer.x or 0) + layer.offsetx + self.offsetx + layer.y = (layer.y or 0) + layer.offsety + self.offsety + layer.update = function() end + + if layer.type == "tilelayer" then + self:setTileData(layer) + self:setSpriteBatches(layer) + layer.draw = function() self:drawTileLayer(layer) end + elseif layer.type == "objectgroup" then + self:setObjectData(layer) + self:setObjectCoordinates(layer) + self:setObjectSpriteBatches(layer) + layer.draw = function() self:drawObjectLayer(layer) end + elseif layer.type == "imagelayer" then + layer.draw = function() self:drawImageLayer(layer) end + + if layer.image ~= "" then + local formatted_path = utils.format_path(path .. layer.image) + if not STI.cache[formatted_path] then + utils.cache_image(STI, formatted_path) + end + + layer.image = STI.cache[formatted_path] + layer.width = layer.image:getWidth() + layer.height = layer.image:getHeight() + end + end + + self.layers[layer.name] = layer +end + +--- Add Tiles to Tile Layer +-- @param layer The Tile Layer +function Map:setTileData(layer) + if layer.chunks then + for _, chunk in ipairs(layer.chunks) do + self:setTileData(chunk) + end + return + end + + local i = 1 + local map = {} + + for y = 1, layer.height do + map[y] = {} + for x = 1, layer.width do + local gid = layer.data[i] + + -- NOTE: Empty tiles have a GID of 0 + if gid > 0 then + map[y][x] = self.tiles[gid] or self:setFlippedGID(gid) + end + + i = i + 1 + end + end + + layer.data = map +end + +--- Add Objects to Layer +-- @param layer The Object Layer +function Map:setObjectData(layer) + for _, object in ipairs(layer.objects) do + object.layer = layer + self.objects[object.id] = object + end +end + +--- Correct position and orientation of Objects in an Object Layer +-- @param layer The Object Layer +function Map:setObjectCoordinates(layer) + for _, object in ipairs(layer.objects) do + local x = layer.x + object.x + local y = layer.y + object.y + local w = object.width + local h = object.height + local cos = math.cos(math.rad(object.rotation)) + local sin = math.sin(math.rad(object.rotation)) + + if object.shape == "rectangle" and not object.gid then + object.rectangle = {} + + local vertices = { + { x=x, y=y }, + { x=x + w, y=y }, + { x=x + w, y=y + h }, + { x=x, y=y + h }, + } + + for _, vertex in ipairs(vertices) do + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) + table.insert(object.rectangle, { x = vertex.x, y = vertex.y }) + end + elseif object.shape == "ellipse" then + object.ellipse = {} + local vertices = utils.convert_ellipse_to_polygon(x, y, w, h) + + for _, vertex in ipairs(vertices) do + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) + table.insert(object.ellipse, { x = vertex.x, y = vertex.y }) + end + elseif object.shape == "polygon" then + for _, vertex in ipairs(object.polygon) do + vertex.x = vertex.x + x + vertex.y = vertex.y + y + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) + end + elseif object.shape == "polyline" then + for _, vertex in ipairs(object.polyline) do + vertex.x = vertex.x + x + vertex.y = vertex.y + y + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) + end + end + end +end + +--- Convert tile location to tile instance location +-- @param layer Tile layer +-- @param tile Tile +-- @param x Tile location on X axis (in tiles) +-- @param y Tile location on Y axis (in tiles) +-- @return number Tile instance location on X axis (in pixels) +-- @return number Tile instance location on Y axis (in pixels) +function Map:getLayerTilePosition(layer, tile, x, y) + local tileW = self.tilewidth + local tileH = self.tileheight + local tileX, tileY + + if self.orientation == "orthogonal" then + tileX = (x - 1) * tileW + tile.offset.x + tileY = (y - 0) * tileH + tile.offset.y - tile.height + tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH) + elseif self.orientation == "isometric" then + tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2 + tileY = (x + y - 2) * (tileH / 2) + tile.offset.y + else + local sideLen = self.hexsidelength or 0 + if self.staggeraxis == "y" then + if self.staggerindex == "odd" then + if y % 2 == 0 then + tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x + else + tileX = (x - 1) * tileW + tile.offset.x + end + else + if y % 2 == 0 then + tileX = (x - 1) * tileW + tile.offset.x + else + tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x + end + end + + local rowH = tileH - (tileH - sideLen) / 2 + tileY = (y - 1) * rowH + tile.offset.y + else + if self.staggerindex == "odd" then + if x % 2 == 0 then + tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y + else + tileY = (y - 1) * tileH + tile.offset.y + end + else + if x % 2 == 0 then + tileY = (y - 1) * tileH + tile.offset.y + else + tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y + end + end + + local colW = tileW - (tileW - sideLen) / 2 + tileX = (x - 1) * colW + tile.offset.x + end + end + + return tileX, tileY +end + +--- Place new tile instance +-- @param layer Tile layer +-- @param chunk Layer chunk +-- @param tile Tile +-- @param number Tile location on X axis (in tiles) +-- @param number Tile location on Y axis (in tiles) +function Map:addNewLayerTile(layer, chunk, tile, x, y) + local tileset = tile.tileset + local image = self.tilesets[tile.tileset].image + local batches + local size + + if chunk then + batches = chunk.batches + size = chunk.width * chunk.height + else + batches = layer.batches + size = layer.width * layer.height + end + + batches[tileset] = batches[tileset] or lg.newSpriteBatch(image, size) + + local batch = batches[tileset] + local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) + + local instance = { + layer = layer, + chunk = chunk, + gid = tile.gid, + x = tileX, + y = tileY, + r = tile.r, + oy = 0 + } + + -- NOTE: STI can run headless so it is not guaranteed that a batch exists. + if batch then + instance.batch = batch + instance.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy) + end + + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} + table.insert(self.tileInstances[tile.gid], instance) +end + +function Map:set_batches(layer, chunk) + if chunk then + chunk.batches = {} + else + layer.batches = {} + end + + if self.orientation == "orthogonal" or self.orientation == "isometric" then + local offsetX = chunk and chunk.x or 0 + local offsetY = chunk and chunk.y or 0 + + local startX = 1 + local startY = 1 + local endX = chunk and chunk.width or layer.width + local endY = chunk and chunk.height or layer.height + local incrementX = 1 + local incrementY = 1 + + -- Determine order to add tiles to sprite batch + -- Defaults to right-down + if self.renderorder == "right-up" then + startY, endY, incrementY = endY, startY, -1 + elseif self.renderorder == "left-down" then + startX, endX, incrementX = endX, startX, -1 + elseif self.renderorder == "left-up" then + startX, endX, incrementX = endX, startX, -1 + startY, endY, incrementY = endY, startY, -1 + end + + for y = startY, endY, incrementY do + for x = startX, endX, incrementX do + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil + local tile + if chunk then + tile = chunk.data[y][x] + else + tile = layer.data[y][x] + end + + if tile then + self:addNewLayerTile(layer, chunk, tile, x + offsetX, y + offsetY) + end + end + end + else + if self.staggeraxis == "y" then + for y = 1, (chunk and chunk.height or layer.height) do + for x = 1, (chunk and chunk.width or layer.width) do + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil + local tile + if chunk then + tile = chunk.data[y][x] + else + tile = layer.data[y][x] + end + + if tile then + self:addNewLayerTile(layer, chunk, tile, x, y) + end + end + end + else + local i = 0 + local _x + + if self.staggerindex == "odd" then + _x = 1 + else + _x = 2 + end + + while i < (chunk and chunk.width * chunk.height or layer.width * layer.height) do + for _y = 1, (chunk and chunk.height or layer.height) + 0.5, 0.5 do + local y = floor(_y) + + for x = _x, (chunk and chunk.width or layer.width), 2 do + i = i + 1 + + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil + local tile + if chunk then + tile = chunk.data[y][x] + else + tile = layer.data[y][x] + end + + if tile then + self:addNewLayerTile(layer, chunk, tile, x, y) + end + end + + if _x == 1 then + _x = 2 + else + _x = 1 + end + end + end + end + end +end + +--- Batch Tiles in Tile Layer for improved draw speed +-- @param layer The Tile Layer +function Map:setSpriteBatches(layer) + if layer.chunks then + for _, chunk in ipairs(layer.chunks) do + self:set_batches(layer, chunk) + end + return + end + + self:set_batches(layer) +end + +--- Batch Tiles in Object Layer for improved draw speed +-- @param layer The Object Layer +function Map:setObjectSpriteBatches(layer) + local newBatch = lg.newSpriteBatch + local batches = {} + + if layer.draworder == "topdown" then + table.sort(layer.objects, function(a, b) + return a.y + a.height < b.y + b.height + end) + end + + for _, object in ipairs(layer.objects) do + if object.gid then + local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid) + local tileset = tile.tileset + local image = self.tilesets[tileset].image + + batches[tileset] = batches[tileset] or newBatch(image) + + local sx = object.width / tile.width + local sy = object.height / tile.height + + -- Tiled rotates around bottom left corner, where love2D rotates around top left corner + local ox = 0 + local oy = tile.height + + local batch = batches[tileset] + local tileX = object.x + tile.offset.x + local tileY = object.y + tile.offset.y + local tileR = math.rad(object.rotation) + + -- Compensation for scale/rotation shift + if tile.sx == -1 then + tileX = tileX + object.width + + if tileR ~= 0 then + tileX = tileX - object.width + ox = ox + tile.width + end + end + + if tile.sy == -1 then + tileY = tileY - object.height + + if tileR ~= 0 then + tileY = tileY + object.width + oy = oy - tile.width + end + end + + local instance = { + id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, ox, oy), + batch = batch, + layer = layer, + gid = tile.gid, + x = tileX, + y = tileY - oy, + r = tileR, + oy = oy + } + + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} + table.insert(self.tileInstances[tile.gid], instance) + end + end + + layer.batches = batches +end + +--- Create a Custom Layer to place userdata in (such as player sprites) +-- @param name Name of Custom Layer +-- @param index Draw order within Layer stack +-- @return table Custom Layer +function Map:addCustomLayer(name, index) + index = index or #self.layers + 1 + local layer = { + type = "customlayer", + name = name, + visible = true, + opacity = 1, + properties = {}, + } + + function layer.draw() end + function layer.update() end + + table.insert(self.layers, index, layer) + self.layers[name] = self.layers[index] + + return layer +end + +--- Convert another Layer into a Custom Layer +-- @param index Index or name of Layer to convert +-- @return table Custom Layer +function Map:convertToCustomLayer(index) + local layer = assert(self.layers[index], "Layer not found: " .. index) + + layer.type = "customlayer" + layer.x = nil + layer.y = nil + layer.width = nil + layer.height = nil + layer.encoding = nil + layer.data = nil + layer.chunks = nil + layer.objects = nil + layer.image = nil + + function layer.draw() end + function layer.update() end + + return layer +end + +--- Remove a Layer from the Layer stack +-- @param index Index or name of Layer to remove +function Map:removeLayer(index) + local layer = assert(self.layers[index], "Layer not found: " .. index) + + if type(index) == "string" then + for i, l in ipairs(self.layers) do + if l.name == index then + table.remove(self.layers, i) + self.layers[index] = nil + break + end + end + else + local name = self.layers[index].name + table.remove(self.layers, index) + self.layers[name] = nil + end + + -- Remove layer batches + if layer.batches then + for _, batch in pairs(layer.batches) do + self.freeBatchSprites[batch] = nil + end + end + + -- Remove chunk batches + if layer.chunks then + for _, chunk in ipairs(layer.chunks) do + for _, batch in pairs(chunk.batches) do + self.freeBatchSprites[batch] = nil + end + end + end + + -- Remove tile instances + if layer.type == "tilelayer" then + for _, tiles in pairs(self.tileInstances) do + for i = #tiles, 1, -1 do + local tile = tiles[i] + if tile.layer == layer then + table.remove(tiles, i) + end + end + end + end + + -- Remove objects + if layer.objects then + for i, object in pairs(self.objects) do + if object.layer == layer then + self.objects[i] = nil + end + end + end +end + +--- Animate Tiles and update every Layer +-- @param dt Delta Time +function Map:update(dt) + for _, tile in pairs(self.tiles) do + local update = false + + if tile.animation then + tile.time = tile.time + dt * 1000 + + while tile.time > tonumber(tile.animation[tile.frame].duration) do + update = true + tile.time = tile.time - tonumber(tile.animation[tile.frame].duration) + tile.frame = tile.frame + 1 + + if tile.frame > #tile.animation then tile.frame = 1 end + end + + if update and self.tileInstances[tile.gid] then + for _, j in pairs(self.tileInstances[tile.gid]) do + local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid] + j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy) + end + end + end + end + + for _, layer in ipairs(self.layers) do + layer:update(dt) + end +end + +--- Draw every Layer +-- @param tx Translate on X +-- @param ty Translate on Y +-- @param sx Scale on X +-- @param sy Scale on Y +function Map:draw(tx, ty, sx, sy) + local current_canvas = lg.getCanvas() + lg.setCanvas(self.canvas) + lg.clear() + + -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues + -- Map is translated to correct position so the right section is drawn + lg.push() + lg.origin() + + --[[ + This snippet comes from 'monolifed' on the Love2D forums, + however it was more or less exactly the same code I was already writing + to implement the same parallax scrolling. I found his before + testing and polishing mine + https://love2d.org/forums/viewtopic.php?p=238378#p238378 + + previous code commented below the new. + + ]] + + tx, ty = tx or 0, ty or 0 + + for _, layer in ipairs(self.layers) do + if layer.visible and layer.opacity > 0 then + local px, py = layer.parallaxx or 1, layer.parallaxy or 1 + px, py = math.floor(tx * px), math.floor(ty * py) + lg.translate(px, py) + self:drawLayer(layer) + lg.translate(-px, -py) + end + end + + --[[ + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) + + for _, layer in ipairs(self.layers) do + if layer.visible and layer.opacity > 0 then + self:drawLayer(layer) + end + end + ]] + + lg.pop() + + -- Draw canvas at 0,0; this fixes scissoring issues + -- Map is scaled to correct scale so the right section is shown + lg.push() + lg.origin() + lg.scale(sx or 1, sy or sx or 1) + + lg.setCanvas(current_canvas) + lg.draw(self.canvas) + + lg.pop() +end + +--- Draw an individual Layer +-- @param layer The Layer to draw +function Map.drawLayer(_, layer) + local r,g,b,a = lg.getColor() + -- if the layer has a tintcolor set + if layer.tintcolor then + r, g, b, a = unpack(layer.tintcolor) + a = a or 255 -- alpha may not be specified + lg.setColor(r/255, g/255, b/255, a/255) -- Tiled uses 0-255 + -- if a tintcolor is not given just use the current color + else + lg.setColor(r, g, b, a * layer.opacity) + end + layer:draw() + lg.setColor(r,g,b,a) +end + +--- Default draw function for Tile Layers +-- @param layer The Tile Layer to draw +function Map:drawTileLayer(layer) + if type(layer) == "string" or type(layer) == "number" then + layer = self.layers[layer] + end + + assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer") + + -- NOTE: This does not take into account any sort of draw range clipping and will always draw every chunk + if layer.chunks then + for _, chunk in ipairs(layer.chunks) do + for _, batch in pairs(chunk.batches) do + lg.draw(batch, 0, 0) + end + end + + return + end + + for _, batch in pairs(layer.batches) do + lg.draw(batch, floor(layer.x), floor(layer.y)) + end +end + +--- Default draw function for Object Layers +-- @param layer The Object Layer to draw +function Map:drawObjectLayer(layer) + if type(layer) == "string" or type(layer) == "number" then + layer = self.layers[layer] + end + + assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup") + + local line = { 160, 160, 160, 255 * layer.opacity } + local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 } + local r,g,b,a = lg.getColor() + local reset = { r, g, b, a * layer.opacity } + + local function sortVertices(obj) + local vertex = {} + + for _, v in ipairs(obj) do + table.insert(vertex, v.x) + table.insert(vertex, v.y) + end + + return vertex + end + + local function drawShape(obj, shape) + local vertex = sortVertices(obj) + + if shape == "polyline" then + lg.setColor(line) + lg.line(vertex) + return + elseif shape == "polygon" then + lg.setColor(fill) + if not love.math.isConvex(vertex) then + local triangles = love.math.triangulate(vertex) + for _, triangle in ipairs(triangles) do + lg.polygon("fill", triangle) + end + else + lg.polygon("fill", vertex) + end + else + lg.setColor(fill) + lg.polygon("fill", vertex) + end + + lg.setColor(line) + lg.polygon("line", vertex) + end + + for _, object in ipairs(layer.objects) do + if object.visible then + if object.shape == "rectangle" and not object.gid then + drawShape(object.rectangle, "rectangle") + elseif object.shape == "ellipse" then + drawShape(object.ellipse, "ellipse") + elseif object.shape == "polygon" then + drawShape(object.polygon, "polygon") + elseif object.shape == "polyline" then + drawShape(object.polyline, "polyline") + elseif object.shape == "point" then + lg.points(object.x, object.y) + end + end + end + + lg.setColor(reset) + for _, batch in pairs(layer.batches) do + lg.draw(batch, 0, 0) + end + lg.setColor(r,g,b,a) +end + +--- Default draw function for Image Layers +-- @param layer The Image Layer to draw +function Map:drawImageLayer(layer) + if type(layer) == "string" or type(layer) == "number" then + layer = self.layers[layer] + end + + assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer") + + if layer.image ~= "" then + lg.draw(layer.image, layer.x, layer.y) + -- we need pixel sizes for drawing + local imagewidth, imageheight = layer.image:getDimensions() + -- if we're repeating on the Y axis... + if layer.repeaty then + local x = imagewidth + local y = imageheight + while y < self.height * self.tileheight do + lg.draw(layer.image, x, y) + -- if we are *also* repeating on X + if layer.repeatx then + x = x + imagewidth + while x < self.width * self.tilewidth do + lg.draw(layer.image, x, y) + x = x + imagewidth + end + end + y = y + imageheight + end + -- if we're repeating on X alone... + elseif layer.repeatx then + local x = imagewidth + while x < self.width * self.tilewidth do + lg.draw(layer.image, x, layer.y) + x = x + imagewidth + end + end + end +end + +--- Resize the drawable area of the Map +-- @param w The new width of the drawable area (in pixels) +-- @param h The new Height of the drawable area (in pixels) +function Map:resize(w, h) + if lg.isCreated then + w = w or lg.getWidth() + h = h or lg.getHeight() + + self.canvas = lg.newCanvas(w, h) + self.canvas:setFilter("nearest", "nearest") + end +end + +--- Create flipped or rotated Tiles based on bitop flags +-- @param gid The flagged Global ID +-- @return table Flipped Tile +function Map:setFlippedGID(gid) + local bit31 = 2147483648 + local bit30 = 1073741824 + local bit29 = 536870912 + local flipX = false + local flipY = false + local flipD = false + local realgid = gid + + if realgid >= bit31 then + realgid = realgid - bit31 + flipX = not flipX + end + + if realgid >= bit30 then + realgid = realgid - bit30 + flipY = not flipY + end + + if realgid >= bit29 then + realgid = realgid - bit29 + flipD = not flipD + end + + local tile = self.tiles[realgid] + local data = { + id = tile.id, + gid = gid, + tileset = tile.tileset, + frame = tile.frame, + time = tile.time, + width = tile.width, + height = tile.height, + offset = tile.offset, + quad = tile.quad, + properties = tile.properties, + terrain = tile.terrain, + animation = tile.animation, + sx = tile.sx, + sy = tile.sy, + r = tile.r, + } + + if flipX then + if flipY and flipD then + data.r = math.rad(-90) + data.sy = -1 + elseif flipY then + data.sx = -1 + data.sy = -1 + elseif flipD then + data.r = math.rad(90) + else + data.sx = -1 + end + elseif flipY then + if flipD then + data.r = math.rad(-90) + else + data.sy = -1 + end + elseif flipD then + data.r = math.rad(90) + data.sy = -1 + end + + self.tiles[gid] = data + + return self.tiles[gid] +end + +--- Get custom properties from Layer +-- @param layer The Layer +-- @return table List of properties +function Map:getLayerProperties(layer) + local l = self.layers[layer] + + if not l then + return {} + end + + return l.properties +end + +--- Get custom properties from Tile +-- @param layer The Layer that the Tile belongs to +-- @param x The X axis location of the Tile (in tiles) +-- @param y The Y axis location of the Tile (in tiles) +-- @return table List of properties +function Map:getTileProperties(layer, x, y) + local tile = self.layers[layer].data[y][x] + + if not tile then + return {} + end + + return tile.properties +end + +--- Get custom properties from Object +-- @param layer The Layer that the Object belongs to +-- @param object The index or name of the Object +-- @return table List of properties +function Map:getObjectProperties(layer, object) + local o = self.layers[layer].objects + + if type(object) == "number" then + o = o[object] + else + for _, v in ipairs(o) do + if v.name == object then + o = v + break + end + end + end + + if not o then + return {} + end + + return o.properties +end + +--- Change a tile in a layer to another tile +-- @param layer The Layer that the Tile belongs to +-- @param x The X axis location of the Tile (in tiles) +-- @param y The Y axis location of the Tile (in tiles) +-- @param gid The gid of the new tile +function Map:setLayerTile(layer, x, y, gid) + layer = self.layers[layer] + + layer.data[y] = layer.data[y] or {} + local tile = layer.data[y][x] + local instance + if tile then + local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) + for _, inst in pairs(self.tileInstances[tile.gid]) do + if inst.x == tileX and inst.y == tileY then + instance = inst + break + end + end + end + + if tile == self.tiles[gid] then + return + end + + tile = self.tiles[gid] + + if instance then + self:swapTile(instance, tile) + else + self:addNewLayerTile(layer, tile, x, y) + end + layer.data[y][x] = tile +end + +--- Swap a tile in a spritebatch +-- @param instance The current Instance object we want to replace +-- @param tile The Tile object we want to use +-- @return none +function Map:swapTile(instance, tile) + -- Update sprite batch + if instance.batch then + if tile then + instance.batch:set( + instance.id, + tile.quad, + instance.x, + instance.y, + tile.r, + tile.sx, + tile.sy + ) + else + instance.batch:set( + instance.id, + instance.x, + instance.y, + 0, + 0) + + self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {} + table.insert(self.freeBatchSprites[instance.batch], instance) + end + end + + -- Remove old tile instance + for i, ins in ipairs(self.tileInstances[instance.gid]) do + if ins.batch == instance.batch and ins.id == instance.id then + table.remove(self.tileInstances[instance.gid], i) + break + end + end + + -- Add new tile instance + if tile then + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} + + local freeBatchSprites = self.freeBatchSprites[instance.batch] + local newInstance + if freeBatchSprites and #freeBatchSprites > 0 then + newInstance = freeBatchSprites[#freeBatchSprites] + freeBatchSprites[#freeBatchSprites] = nil + else + newInstance = {} + end + + newInstance.layer = instance.layer + newInstance.batch = instance.batch + newInstance.id = instance.id + newInstance.gid = tile.gid or 0 + newInstance.x = instance.x + newInstance.y = instance.y + newInstance.r = tile.r or 0 + newInstance.oy = tile.r ~= 0 and tile.height or 0 + table.insert(self.tileInstances[tile.gid], newInstance) + end +end + +--- Convert tile location to pixel location +-- @param x The X axis location of the point (in tiles) +-- @param y The Y axis location of the point (in tiles) +-- @return number The X axis location of the point (in pixels) +-- @return number The Y axis location of the point (in pixels) +function Map:convertTileToPixel(x,y) + if self.orientation == "orthogonal" then + local tileW = self.tilewidth + local tileH = self.tileheight + return + x * tileW, + y * tileH + elseif self.orientation == "isometric" then + local mapH = self.height + local tileW = self.tilewidth + local tileH = self.tileheight + local offsetX = mapH * tileW / 2 + return + (x - y) * tileW / 2 + offsetX, + (x + y) * tileH / 2 + elseif self.orientation == "staggered" or + self.orientation == "hexagonal" then + local tileW = self.tilewidth + local tileH = self.tileheight + local sideLen = self.hexsidelength or 0 + + if self.staggeraxis == "x" then + return + x * tileW, + ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0) + else + return + ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0), + y * tileH + end + end +end + +--- Convert pixel location to tile location +-- @param x The X axis location of the point (in pixels) +-- @param y The Y axis location of the point (in pixels) +-- @return number The X axis location of the point (in tiles) +-- @return number The Y axis location of the point (in tiles) +function Map:convertPixelToTile(x, y) + if self.orientation == "orthogonal" then + local tileW = self.tilewidth + local tileH = self.tileheight + return + x / tileW, + y / tileH + elseif self.orientation == "isometric" then + local mapH = self.height + local tileW = self.tilewidth + local tileH = self.tileheight + local offsetX = mapH * tileW / 2 + return + y / tileH + (x - offsetX) / tileW, + y / tileH - (x - offsetX) / tileW + elseif self.orientation == "staggered" then + local staggerX = self.staggeraxis == "x" + local even = self.staggerindex == "even" + + local function topLeft(x, y) + if staggerX then + if ceil(x) % 2 == 1 and even then + return x - 1, y + else + return x - 1, y - 1 + end + else + if ceil(y) % 2 == 1 and even then + return x, y - 1 + else + return x - 1, y - 1 + end + end + end + + local function topRight(x, y) + if staggerX then + if ceil(x) % 2 == 1 and even then + return x + 1, y + else + return x + 1, y - 1 + end + else + if ceil(y) % 2 == 1 and even then + return x + 1, y - 1 + else + return x, y - 1 + end + end + end + + local function bottomLeft(x, y) + if staggerX then + if ceil(x) % 2 == 1 and even then + return x - 1, y + 1 + else + return x - 1, y + end + else + if ceil(y) % 2 == 1 and even then + return x, y + 1 + else + return x - 1, y + 1 + end + end + end + + local function bottomRight(x, y) + if staggerX then + if ceil(x) % 2 == 1 and even then + return x + 1, y + 1 + else + return x + 1, y + end + else + if ceil(y) % 2 == 1 and even then + return x + 1, y + 1 + else + return x, y + 1 + end + end + end + + local tileW = self.tilewidth + local tileH = self.tileheight + + if staggerX then + x = x - (even and tileW / 2 or 0) + else + y = y - (even and tileH / 2 or 0) + end + + local halfH = tileH / 2 + local ratio = tileH / tileW + local referenceX = ceil(x / tileW) + local referenceY = ceil(y / tileH) + local relativeX = x - referenceX * tileW + local relativeY = y - referenceY * tileH + + if (halfH - relativeX * ratio > relativeY) then + return topLeft(referenceX, referenceY) + elseif (-halfH + relativeX * ratio > relativeY) then + return topRight(referenceX, referenceY) + elseif (halfH + relativeX * ratio < relativeY) then + return bottomLeft(referenceX, referenceY) + elseif (halfH * 3 - relativeX * ratio < relativeY) then + return bottomRight(referenceX, referenceY) + end + + return referenceX, referenceY + elseif self.orientation == "hexagonal" then + local staggerX = self.staggeraxis == "x" + local even = self.staggerindex == "even" + local tileW = self.tilewidth + local tileH = self.tileheight + local sideLenX = 0 + local sideLenY = 0 + + local colW = tileW / 2 + local rowH = tileH / 2 + if staggerX then + sideLenX = self.hexsidelength + x = x - (even and tileW or (tileW - sideLenX) / 2) + colW = colW - (colW - sideLenX / 2) / 2 + else + sideLenY = self.hexsidelength + y = y - (even and tileH or (tileH - sideLenY) / 2) + rowH = rowH - (rowH - sideLenY / 2) / 2 + end + + local referenceX = ceil(x) / (colW * 2) + local referenceY = ceil(y) / (rowH * 2) + + -- If in staggered line, then shift reference by 0.5 of other axes + if staggerX then + if (floor(referenceX) % 2 == 0) == even then + referenceY = referenceY - 0.5 + end + else + if (floor(referenceY) % 2 == 0) == even then + referenceX = referenceX - 0.5 + end + end + + local relativeX = x - referenceX * colW * 2 + local relativeY = y - referenceY * rowH * 2 + local centers + + if staggerX then + local left = sideLenX / 2 + local centerX = left + colW + local centerY = tileH / 2 + + centers = { + { x = left, y = centerY }, + { x = centerX, y = centerY - rowH }, + { x = centerX, y = centerY + rowH }, + { x = centerX + colW, y = centerY }, + } + else + local top = sideLenY / 2 + local centerX = tileW / 2 + local centerY = top + rowH + + centers = { + { x = centerX, y = top }, + { x = centerX - colW, y = centerY }, + { x = centerX + colW, y = centerY }, + { x = centerX, y = centerY + rowH } + } + end + + local nearest = 0 + local minDist = math.huge + + local function len2(ax, ay) + return ax * ax + ay * ay + end + + for i = 1, 4 do + local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY) + + if dc < minDist then + minDist = dc + nearest = i + end + end + + local offsetsStaggerX = { + { x = 1, y = 1 }, + { x = 2, y = 0 }, + { x = 2, y = 1 }, + { x = 3, y = 1 }, + } + + local offsetsStaggerY = { + { x = 1, y = 1 }, + { x = 0, y = 2 }, + { x = 1, y = 2 }, + { x = 1, y = 3 }, + } + + local offsets = staggerX and offsetsStaggerX or offsetsStaggerY + + return + referenceX + offsets[nearest].x, + referenceY + offsets[nearest].y + end +end + +--- A list of individual layers indexed both by draw order and name +-- @table Map.layers +-- @see TileLayer +-- @see ObjectLayer +-- @see ImageLayer +-- @see CustomLayer + +--- A list of individual tiles indexed by Global ID +-- @table Map.tiles +-- @see Tile +-- @see Map.tileInstances + +--- A list of tile instances indexed by Global ID +-- @table Map.tileInstances +-- @see TileInstance +-- @see Tile +-- @see Map.tiles + +--- A list of no-longer-used batch sprites, indexed by batch +--@table Map.freeBatchSprites + +--- A list of individual objects indexed by Global ID +-- @table Map.objects +-- @see Object + +--- @table TileLayer +-- @field name The name of the layer +-- @field x Position on the X axis (in pixels) +-- @field y Position on the Y axis (in pixels) +-- @field width Width of layer (in tiles) +-- @field height Height of layer (in tiles) +-- @field visible Toggle if layer is visible or hidden +-- @field opacity Opacity of layer +-- @field properties Custom properties +-- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles) +-- @field update Update function +-- @field draw Draw function +-- @see Map.layers +-- @see Tile + +--- @table ObjectLayer +-- @field name The name of the layer +-- @field x Position on the X axis (in pixels) +-- @field y Position on the Y axis (in pixels) +-- @field visible Toggle if layer is visible or hidden +-- @field opacity Opacity of layer +-- @field properties Custom properties +-- @field objects List of objects indexed by draw order +-- @field update Update function +-- @field draw Draw function +-- @see Map.layers +-- @see Object + +--- @table ImageLayer +-- @field name The name of the layer +-- @field x Position on the X axis (in pixels) +-- @field y Position on the Y axis (in pixels) +-- @field visible Toggle if layer is visible or hidden +-- @field opacity Opacity of layer +-- @field properties Custom properties +-- @field image Image to be drawn +-- @field update Update function +-- @field draw Draw function +-- @see Map.layers + +--- Custom Layers are used to place userdata such as sprites within the draw order of the map. +-- @table CustomLayer +-- @field name The name of the layer +-- @field x Position on the X axis (in pixels) +-- @field y Position on the Y axis (in pixels) +-- @field visible Toggle if layer is visible or hidden +-- @field opacity Opacity of layer +-- @field properties Custom properties +-- @field update Update function +-- @field draw Draw function +-- @see Map.layers +-- @usage +-- -- Create a Custom Layer +-- local spriteLayer = map:addCustomLayer("Sprite Layer", 3) +-- +-- -- Add data to Custom Layer +-- spriteLayer.sprites = { +-- player = { +-- image = lg.newImage("assets/sprites/player.png"), +-- x = 64, +-- y = 64, +-- r = 0, +-- } +-- } +-- +-- -- Update callback for Custom Layer +-- function spriteLayer:update(dt) +-- for _, sprite in pairs(self.sprites) do +-- sprite.r = sprite.r + math.rad(90 * dt) +-- end +-- end +-- +-- -- Draw callback for Custom Layer +-- function spriteLayer:draw() +-- for _, sprite in pairs(self.sprites) do +-- local x = math.floor(sprite.x) +-- local y = math.floor(sprite.y) +-- local r = sprite.r +-- lg.draw(sprite.image, x, y, r) +-- end +-- end + +--- @table Tile +-- @field id Local ID within Tileset +-- @field gid Global ID +-- @field tileset Tileset ID +-- @field quad Quad object +-- @field properties Custom properties +-- @field terrain Terrain data +-- @field animation Animation data +-- @field frame Current animation frame +-- @field time Time spent on current animation frame +-- @field width Width of tile +-- @field height Height of tile +-- @field sx Scale value on the X axis +-- @field sy Scale value on the Y axis +-- @field r Rotation of tile (in radians) +-- @field offset Offset drawing position +-- @field offset.x Offset value on the X axis +-- @field offset.y Offset value on the Y axis +-- @see Map.tiles + +--- @table TileInstance +-- @field batch Spritebatch the Tile Instance belongs to +-- @field id ID within the spritebatch +-- @field gid Global ID +-- @field x Position on the X axis (in pixels) +-- @field y Position on the Y axis (in pixels) +-- @see Map.tileInstances +-- @see Tile + +--- @table Object +-- @field id Global ID +-- @field name Name of object (non-unique) +-- @field shape Shape of object +-- @field x Position of object on X axis (in pixels) +-- @field y Position of object on Y axis (in pixels) +-- @field width Width of object (in pixels) +-- @field height Heigh tof object (in pixels) +-- @field rotation Rotation of object (in radians) +-- @field visible Toggle if object is visible or hidden +-- @field properties Custom properties +-- @field ellipse List of verticies of specific shape +-- @field rectangle List of verticies of specific shape +-- @field polygon List of verticies of specific shape +-- @field polyline List of verticies of specific shape +-- @see Map.objects + +return setmetatable({}, STI) diff --git a/main/libraries/sti/plugins/box2d.lua b/main/libraries/sti/plugins/box2d.lua new file mode 100644 index 0000000..c6d4148 --- /dev/null +++ b/main/libraries/sti/plugins/box2d.lua @@ -0,0 +1,323 @@ +--- Box2D plugin for STI +-- @module box2d +-- @author Landon Manning +-- @copyright 2019 +-- @license MIT/X11 + +local love = _G.love +local utils = require((...):gsub('plugins.box2d', 'utils')) +local lg = require((...):gsub('plugins.box2d', 'graphics')) + +return { + box2d_LICENSE = "MIT/X11", + box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation", + box2d_VERSION = "2.3.2.7", + box2d_DESCRIPTION = "Box2D hooks for STI.", + + --- Initialize Box2D physics world. + -- @param world The Box2D world to add objects to. + box2d_init = function(map, world) + assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.") + + local body = love.physics.newBody(world, map.offsetx, map.offsety) + local collision = { + body = body, + } + + local function addObjectToWorld(objshape, vertices, userdata, object) + local shape + + if objshape == "polyline" then + if #vertices == 4 then + shape = love.physics.newEdgeShape(unpack(vertices)) + else + shape = love.physics.newChainShape(false, unpack(vertices)) + end + else + shape = love.physics.newPolygonShape(unpack(vertices)) + end + + local currentBody = body + --dynamic are objects/players etc. + if userdata.properties.dynamic == true then + currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic') + -- static means it shouldn't move. Things like walls/ground. + elseif userdata.properties.static == true then + currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'static') + -- kinematic means that the object is static in the game world but effects other bodies + elseif userdata.properties.kinematic == true then + currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'kinematic') + end + + local fixture = love.physics.newFixture(currentBody, shape) + fixture:setUserData(userdata) + + -- Set some custom properties from userdata (or use default set by box2d) + fixture:setFriction(userdata.properties.friction or 0.2) + fixture:setRestitution(userdata.properties.restitution or 0.0) + fixture:setSensor(userdata.properties.sensor or false) + fixture:setFilterData( + userdata.properties.categories or 1, + userdata.properties.mask or 65535, + userdata.properties.group or 0 + ) + + local obj = { + object = object, + body = currentBody, + shape = shape, + fixture = fixture, + } + + table.insert(collision, obj) + end + + local function getPolygonVertices(object) + local vertices = {} + for _, vertex in ipairs(object.polygon) do + table.insert(vertices, vertex.x) + table.insert(vertices, vertex.y) + end + + return vertices + end + + local function calculateObjectPosition(object, tile) + local o = { + shape = object.shape, + x = (object.dx or object.x) + map.offsetx, + y = (object.dy or object.y) + map.offsety, + w = object.width, + h = object.height, + polygon = object.polygon or object.polyline or object.ellipse or object.rectangle + } + + local userdata = { + object = o, + properties = object.properties + } + + o.r = object.rotation or 0 + if o.shape == "rectangle" then + local cos = math.cos(math.rad(o.r)) + local sin = math.sin(math.rad(o.r)) + local oy = 0 + + if object.gid then + local tileset = map.tilesets[map.tiles[object.gid].tileset] + local lid = object.gid - tileset.firstgid + local t = {} + + -- This fixes a height issue + o.y = o.y + map.tiles[object.gid].offset.y + oy = o.h + + for _, tt in ipairs(tileset.tiles) do + if tt.id == lid then + t = tt + break + end + end + + if t.objectGroup then + for _, obj in ipairs(t.objectGroup.objects) do + -- Every object in the tile + calculateObjectPosition(obj, object) + end + + return + else + o.w = map.tiles[object.gid].width + o.h = map.tiles[object.gid].height + end + end + + o.polygon = { + { x=o.x+0, y=o.y+0 }, + { x=o.x+o.w, y=o.y+0 }, + { x=o.x+o.w, y=o.y+o.h }, + { x=o.x+0, y=o.y+o.h } + } + + for _, vertex in ipairs(o.polygon) do + vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin, oy) + end + + local vertices = getPolygonVertices(o) + addObjectToWorld(o.shape, vertices, userdata, tile or object) + elseif o.shape == "ellipse" then + if not o.polygon then + o.polygon = utils.convert_ellipse_to_polygon(o.x, o.y, o.w, o.h) + end + local vertices = getPolygonVertices(o) + local triangles = love.math.triangulate(vertices) + + for _, triangle in ipairs(triangles) do + addObjectToWorld(o.shape, triangle, userdata, tile or object) + end + elseif o.shape == "polygon" then + -- Recalculate collision polygons inside tiles + if tile then + local cos = math.cos(math.rad(o.r)) + local sin = math.sin(math.rad(o.r)) + for _, vertex in ipairs(o.polygon) do + vertex.x = vertex.x + o.x + vertex.y = vertex.y + o.y + vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin) + end + end + + local vertices = getPolygonVertices(o) + local triangles = love.math.triangulate(vertices) + + for _, triangle in ipairs(triangles) do + addObjectToWorld(o.shape, triangle, userdata, tile or object) + end + elseif o.shape == "polyline" then + local vertices = getPolygonVertices(o) + addObjectToWorld(o.shape, vertices, userdata, tile or object) + end + end + + for _, tile in pairs(map.tiles) do + if map.tileInstances[tile.gid] then + for _, instance in ipairs(map.tileInstances[tile.gid]) do + -- Every object in every instance of a tile + if tile.objectGroup then + for _, object in ipairs(tile.objectGroup.objects) do + if object.properties.collidable == true then + object = utils.deepCopy(object) + object.dx = instance.x + object.x + object.dy = instance.y + object.y + calculateObjectPosition(object, instance) + end + end + end + + -- Every instance of a tile + if tile.properties.collidable == true then + local object = { + shape = "rectangle", + x = instance.x, + y = instance.y, + width = map.tilewidth, + height = map.tileheight, + properties = tile.properties + } + + calculateObjectPosition(object, instance) + end + end + end + end + + for _, layer in ipairs(map.layers) do + -- Entire layer + if layer.properties.collidable == true then + if layer.type == "tilelayer" then + for gid, tiles in pairs(map.tileInstances) do + local tile = map.tiles[gid] + local tileset = map.tilesets[tile.tileset] + + for _, instance in ipairs(tiles) do + if instance.layer == layer then + local object = { + shape = "rectangle", + x = instance.x, + y = instance.y, + width = tileset.tilewidth, + height = tileset.tileheight, + properties = tile.properties + } + + calculateObjectPosition(object, instance) + end + end + end + elseif layer.type == "objectgroup" then + for _, object in ipairs(layer.objects) do + calculateObjectPosition(object) + end + elseif layer.type == "imagelayer" then + local object = { + shape = "rectangle", + x = layer.x or 0, + y = layer.y or 0, + width = layer.width, + height = layer.height, + properties = layer.properties + } + + calculateObjectPosition(object) + end + end + + -- Individual objects + if layer.type == "objectgroup" then + for _, object in ipairs(layer.objects) do + if object.properties.collidable == true then + calculateObjectPosition(object) + end + end + end + end + + map.box2d_collision = collision + end, + + --- Remove Box2D fixtures and shapes from world. + -- @param index The index or name of the layer being removed + box2d_removeLayer = function(map, index) + local layer = assert(map.layers[index], "Layer not found: " .. index) + local collision = map.box2d_collision + + -- Remove collision objects + for i = #collision, 1, -1 do + local obj = collision[i] + + if obj.object.layer == layer then + obj.fixture:destroy() + table.remove(collision, i) + end + end + end, + + --- Draw Box2D physics world. + -- @param tx Translate on X + -- @param ty Translate on Y + -- @param sx Scale on X + -- @param sy Scale on Y + box2d_draw = function(map, tx, ty, sx, sy) + local collision = map.box2d_collision + + lg.push() + lg.scale(sx or 1, sy or sx or 1) + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) + + for _, obj in ipairs(collision) do + local points = {obj.body:getWorldPoints(obj.shape:getPoints())} + local shape_type = obj.shape:getType() + + if shape_type == "edge" or shape_type == "chain" then + love.graphics.line(points) + elseif shape_type == "polygon" then + love.graphics.polygon("line", points) + else + error("sti box2d plugin does not support "..shape_type.." shapes") + end + end + + lg.pop() + end +} + +--- Custom Properties in Tiled are used to tell this plugin what to do. +-- @table Properties +-- @field collidable set to true, can be used on any Layer, Tile, or Object +-- @field sensor set to true, can be used on any Tile or Object that is also collidable +-- @field dynamic set to true, can be used on any Tile or Object +-- @field friction can be used to define the friction of any Object +-- @field restitution can be used to define the restitution of any Object +-- @field categories can be used to set the filter Category of any Object +-- @field mask can be used to set the filter Mask of any Object +-- @field group can be used to set the filter Group of any Object diff --git a/main/libraries/sti/plugins/bump.lua b/main/libraries/sti/plugins/bump.lua new file mode 100644 index 0000000..1d4b828 --- /dev/null +++ b/main/libraries/sti/plugins/bump.lua @@ -0,0 +1,193 @@ +--- Bump.lua plugin for STI +-- @module bump.lua +-- @author David Serrano (BobbyJones|FrenchFryLord) +-- @copyright 2019 +-- @license MIT/X11 + +local lg = require((...):gsub('plugins.bump', 'graphics')) + +return { + bump_LICENSE = "MIT/X11", + bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation", + bump_VERSION = "3.1.7.1", + bump_DESCRIPTION = "Bump hooks for STI.", + + --- Adds each collidable tile to the Bump world. + -- @param world The Bump world to add objects to. + -- @return collidables table containing the handles to the objects in the Bump world. + bump_init = function(map, world) + local collidables = {} + + for _, tileset in ipairs(map.tilesets) do + for _, tile in ipairs(tileset.tiles) do + local gid = tileset.firstgid + tile.id + + if map.tileInstances[gid] then + for _, instance in ipairs(map.tileInstances[gid]) do + -- Every object in every instance of a tile + if tile.objectGroup then + for _, object in ipairs(tile.objectGroup.objects) do + if object.properties.collidable == true then + local t = { + name = object.name, + type = object.type, + x = instance.x + map.offsetx + object.x, + y = instance.y + map.offsety + object.y, + width = object.width, + height = object.height, + layer = instance.layer, + properties = object.properties + + } + + world:add(t, t.x, t.y, t.width, t.height) + table.insert(collidables, t) + end + end + end + + -- Every instance of a tile + if tile.properties and tile.properties.collidable == true then + local t = { + x = instance.x + map.offsetx, + y = instance.y + map.offsety, + width = map.tilewidth, + height = map.tileheight, + layer = instance.layer, + type = tile.type, + properties = tile.properties + } + + world:add(t, t.x, t.y, t.width, t.height) + table.insert(collidables, t) + end + end + end + end + end + + for _, layer in ipairs(map.layers) do + -- Entire layer + if layer.properties.collidable == true then + if layer.type == "tilelayer" then + for y, tiles in ipairs(layer.data) do + for x, tile in pairs(tiles) do + + if tile.objectGroup then + for _, object in ipairs(tile.objectGroup.objects) do + if object.properties.collidable == true then + local t = { + name = object.name, + type = object.type, + x = ((x-1) * map.tilewidth + tile.offset.x + map.offsetx) + object.x, + y = ((y-1) * map.tileheight + tile.offset.y + map.offsety) + object.y, + width = object.width, + height = object.height, + layer = layer, + properties = object.properties + } + + world:add(t, t.x, t.y, t.width, t.height) + table.insert(collidables, t) + end + end + end + + + local t = { + x = (x-1) * map.tilewidth + tile.offset.x + map.offsetx, + y = (y-1) * map.tileheight + tile.offset.y + map.offsety, + width = tile.width, + height = tile.height, + layer = layer, + type = tile.type, + properties = tile.properties + } + + world:add(t, t.x, t.y, t.width, t.height) + table.insert(collidables, t) + end + end + elseif layer.type == "imagelayer" then + world:add(layer, layer.x, layer.y, layer.width, layer.height) + table.insert(collidables, layer) + end + end + + -- individual collidable objects in a layer that is not "collidable" + -- or whole collidable objects layer + if layer.type == "objectgroup" then + for _, obj in ipairs(layer.objects) do + if layer.properties.collidable == true or obj.properties.collidable == true then + if obj.shape == "rectangle" then + local t = { + name = obj.name, + type = obj.type, + x = obj.x + map.offsetx, + y = obj.y + map.offsety, + width = obj.width, + height = obj.height, + layer = layer, + properties = obj.properties + } + + if obj.gid then + t.y = t.y - obj.height + end + + world:add(t, t.x, t.y, t.width, t.height) + table.insert(collidables, t) + end -- TODO implement other object shapes? + end + end + end + end + + map.bump_world = world + map.bump_collidables = collidables + end, + + --- Remove layer + -- @param index to layer to be removed + bump_removeLayer = function(map, index) + local layer = assert(map.layers[index], "Layer not found: " .. index) + local collidables = map.bump_collidables + + -- Remove collision objects + for i = #collidables, 1, -1 do + local obj = collidables[i] + + if obj.layer == layer + and ( + layer.properties.collidable == true + or obj.properties.collidable == true + ) then + map.bump_world:remove(obj) + table.remove(collidables, i) + end + end + end, + + --- Draw bump collisions world. + -- @param world bump world holding the tiles geometry + -- @param tx Translate on X + -- @param ty Translate on Y + -- @param sx Scale on X + -- @param sy Scale on Y + bump_draw = function(map, tx, ty, sx, sy) + lg.push() + lg.scale(sx or 1, sy or sx or 1) + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) + + local items = map.bump_world:getItems() + for _, item in ipairs(items) do + lg.rectangle("line", map.bump_world:getRect(item)) + end + + lg.pop() + end +} + +--- Custom Properties in Tiled are used to tell this plugin what to do. +-- @table Properties +-- @field collidable set to true, can be used on any Layer, Tile, or Object diff --git a/main/libraries/sti/utils.lua b/main/libraries/sti/utils.lua new file mode 100644 index 0000000..95e857a --- /dev/null +++ b/main/libraries/sti/utils.lua @@ -0,0 +1,217 @@ +-- Some utility functions that shouldn't be exposed. +local utils = {} + +-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 +function utils.format_path(path) + local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP' + local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/') + local k + + repeat -- /./ -> / + path,k = path:gsub(np_pat2,'/',1) + until k == 0 + + repeat -- A/../ -> (empty) + path,k = path:gsub(np_pat1,'',1) + until k == 0 + + if path == '' then path = '.' end + + return path +end + +-- Compensation for scale/rotation shift +function utils.compensate(tile, tileX, tileY, tileW, tileH) + local compx = 0 + local compy = 0 + + if tile.sx < 0 then compx = tileW end + if tile.sy < 0 then compy = tileH end + + if tile.r > 0 then + tileX = tileX + tileH - compy + tileY = tileY + tileH + compx - tileW + elseif tile.r < 0 then + tileX = tileX + compy + tileY = tileY - compx + tileH + else + tileX = tileX + compx + tileY = tileY + compy + end + + return tileX, tileY +end + +-- Cache images in main STI module +function utils.cache_image(sti, path, image) + image = image or love.graphics.newImage(path) + image:setFilter("nearest", "nearest") + sti.cache[path] = image +end + +-- We just don't know. +function utils.get_tiles(imageW, tileW, margin, spacing) + imageW = imageW - margin + local n = 0 + + while imageW >= tileW do + imageW = imageW - tileW + if n ~= 0 then imageW = imageW - spacing end + if imageW >= 0 then n = n + 1 end + end + + return n +end + +-- Decompress tile layer data +function utils.get_decompressed_data(data) + local ffi = require "ffi" + local d = {} + local decoded = ffi.cast("uint32_t*", data) + + for i = 0, data:len() / ffi.sizeof("uint32_t") do + table.insert(d, tonumber(decoded[i])) + end + + return d +end + +-- Convert a Tiled ellipse object to a LOVE polygon +function utils.convert_ellipse_to_polygon(x, y, w, h, max_segments) + local ceil = math.ceil + local cos = math.cos + local sin = math.sin + + local function calc_segments(segments) + local function vdist(a, b) + local c = { + x = a.x - b.x, + y = a.y - b.y, + } + + return c.x * c.x + c.y * c.y + end + + segments = segments or 64 + local vertices = {} + + local v = { 1, 2, ceil(segments/4-1), ceil(segments/4) } + + local m + if love and love.physics then + m = love.physics.getMeter() + else + m = 32 + end + + for _, i in ipairs(v) do + local angle = (i / segments) * math.pi * 2 + local px = x + w / 2 + cos(angle) * w / 2 + local py = y + h / 2 + sin(angle) * h / 2 + + table.insert(vertices, { x = px / m, y = py / m }) + end + + local dist1 = vdist(vertices[1], vertices[2]) + local dist2 = vdist(vertices[3], vertices[4]) + + -- Box2D threshold + if dist1 < 0.0025 or dist2 < 0.0025 then + return calc_segments(segments-2) + end + + return segments + end + + local segments = calc_segments(max_segments) + local vertices = {} + + table.insert(vertices, { x = x + w / 2, y = y + h / 2 }) + + for i = 0, segments do + local angle = (i / segments) * math.pi * 2 + local px = x + w / 2 + cos(angle) * w / 2 + local py = y + h / 2 + sin(angle) * h / 2 + + table.insert(vertices, { x = px, y = py }) + end + + return vertices +end + +function utils.rotate_vertex(map, vertex, x, y, cos, sin, oy) + if map.orientation == "isometric" then + x, y = utils.convert_isometric_to_screen(map, x, y) + vertex.x, vertex.y = utils.convert_isometric_to_screen(map, vertex.x, vertex.y) + end + + vertex.x = vertex.x - x + vertex.y = vertex.y - y + + return + x + cos * vertex.x - sin * vertex.y, + y + sin * vertex.x + cos * vertex.y - (oy or 0) +end + +--- Project isometric position to cartesian position +function utils.convert_isometric_to_screen(map, x, y) + local mapW = map.width + local tileW = map.tilewidth + local tileH = map.tileheight + local tileX = x / tileH + local tileY = y / tileH + local offsetX = mapW * tileW / 2 + + return + (tileX - tileY) * tileW / 2 + offsetX, + (tileX + tileY) * tileH / 2 +end + +function utils.hex_to_color(hex) + if hex:sub(1, 1) == "#" then + hex = hex:sub(2) + end + + return { + r = tonumber(hex:sub(1, 2), 16) / 255, + g = tonumber(hex:sub(3, 4), 16) / 255, + b = tonumber(hex:sub(5, 6), 16) / 255 + } +end + +function utils.pixel_function(_, _, r, g, b, a) + local mask = utils._TC + + if r == mask.r and + g == mask.g and + b == mask.b then + return r, g, b, 0 + end + + return r, g, b, a +end + +function utils.fix_transparent_color(tileset, path) + local image_data = love.image.newImageData(path) + tileset.image = love.graphics.newImage(image_data) + + if tileset.transparentcolor then + utils._TC = utils.hex_to_color(tileset.transparentcolor) + + image_data:mapPixel(utils.pixel_function) + tileset.image = love.graphics.newImage(image_data) + end +end + +function utils.deepCopy(t) + local copy = {} + for k,v in pairs(t) do + if type(v) == "table" then + v = utils.deepCopy(v) + end + copy[k] = v + end + return copy +end + +return utils diff --git a/main/main.lua b/main/main.lua index 883cbce..7c4848d 100644 --- a/main/main.lua +++ b/main/main.lua @@ -4,8 +4,11 @@ local conf = require('conf') local assets = require('assets') function love.load() - love.graphics.setFont(assets.get_font('Cuneiform36')) - love.audio.play(assets.get_source("intro")) + + sti = require' libraries/sti' + + love.graphics.setFont(assets.get_font('Cuneiform36')) + love.audio.play(assets.get_source("intro")) end function love.update(delta_time) diff --git a/main/maps/debug_map.tmx b/main/maps/debug_map.tmx new file mode 100644 index 0000000..d3d85ce --- /dev/null +++ b/main/maps/debug_map.tmx @@ -0,0 +1,72 @@ + + + + + +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + + + + +844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,844,844,844,844,844,844,844,844,844,0,0,844,844,844,844,844,844,844,844,844,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,844, +844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844,844 + + + diff --git a/main/maps/sumeriangame.tiled-project b/main/maps/sumeriangame.tiled-project new file mode 100644 index 0000000..d0eb592 --- /dev/null +++ b/main/maps/sumeriangame.tiled-project @@ -0,0 +1,14 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "properties": [ + ], + "propertyTypes": [ + ] +} diff --git a/main/maps/tileset.tsx b/main/maps/tileset.tsx new file mode 100644 index 0000000..0d1e92a --- /dev/null +++ b/main/maps/tileset.tsx @@ -0,0 +1,4 @@ + + + + -- 2.49.0