local Map = { _LICENSE = "STI is distributed under the terms of the MIT license. See LICENSE.md.", _URL = "https://github.com/karai17/Simple-Tiled-Implementation", _VERSION = "0.9.7", _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework." } -- modified here local function getFramework() local lf = love.filesystem local lg = love.graphics local lm = love.math local lp = love.physics local framework = {} framework.version = "LOVE" assert(lf, "The love.filesystem module is required for this library.") assert(lg, "The love.graphics module is required for this library.") assert(lm, "The love.math module is required for this library.") function framework.load(file) return assert(lf.load(file), "File not found: " .. file) end function framework.newImage(path) local image = lg.newImage(path) image:setFilter("nearest", "nearest") return image end function framework:newCanvas(w, h) w = w or self.getWidth() h = h or self.getHeight() local canvas = lg.newCanvas(w, h) canvas:setFilter("nearest", "nearest") return canvas end -- Graphics Calls framework.draw = lg.draw framework.getCanvas = lg.getCanvas framework.getHeight = lg.getHeight framework.getWidth = lg.getWidth framework.line = lg.line framework.newSpriteBatch = lg.newSpriteBatch framework.newQuad = lg.newQuad framework.polygon = lg.polygon framework.rectangle = lg.rectangle framework.setColor = lg.setColor framework.setCanvas = lg.setCanvas framework.origin = lg.origin framework.pop = lg.pop framework.push = lg.push -- Math Calls framework.isConvex = lm.isConvex framework.triangulate = lm.triangulate -- Physics Calls if lp then framework.getMeter = lp.getMeter framework.newBody = lp.newBody framework.newChainShape = lp.newChainShape framework.newFixture = lp.newFixture framework.newPolygonShape = lp.newPolygonShape end return framework end local framework = getFramework() -- to here function string.split(s, d) local magic = { "(", ")", ".", "%", "+", "-", "*", "?", "[", "^", "$" } for _, v in ipairs(magic) do if d == v then d = "%"..d break end end local t = {} local i = 0 local f local match = '(.-)' .. d .. '()' if string.find(s, d) == nil then return {s} end for sub, j in string.gmatch(s, match) do i = i + 1 t[i] = sub f = j end if i ~= 0 then t[i+1] = string.sub(s, f) end return t end -- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 local function formatPath(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,'/') until k == 0 repeat -- A/../ -> (empty) path,k = path:gsub(np_pat1,'') until k == 0 if path == '' then path = '.' end return path end local function rotateVertex(v, x, y, cos, sin) local vertex = { x = v.x, y = v.y, } vertex.x = vertex.x - x vertex.y = vertex.y - y local vx = cos * vertex.x - sin * vertex.y local vy = sin * vertex.x + cos * vertex.y return vx + x, vy + y end local function convertEllipseToPolygon(x, y, w, h, max_segments) 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, math.ceil(segments/4-1), math.ceil(segments/4) } local m if framework.getMeter then m = framework.getMeter() else m = 32 end for _, i in ipairs(v) do local angle = (i / segments) * math.pi * 2 local px = x + w / 2 + math.cos(angle) * w / 2 local py = y + h / 2 + math.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 hard-coded 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 + math.cos(angle) * w / 2 local py = y + h / 2 + math.sin(angle) * h / 2 table.insert(vertices, { x = px, y = py }) end return vertices end function Map:init(path, fw) framework = fw self.tiles = {} self.tileInstances = {} self.drawRange = { sx = 1, sy = 1, ex = self.width, ey = self.height, } -- Set tiles, images local gid = 1 for i, tileset in ipairs(self.tilesets) do local image = formatPath(path .. tileset.image) tileset.image = framework.newImage(image) gid = self:setTiles(i, tileset, gid) end -- Set layers for i, layer in ipairs(self.layers) do self:setLayer(layer, path) end end function Map:initWorldCollision(world) assert(framework.newBody, "To use the built-in collision system, please enable the physics module.") local body = framework.newBody(world) local collision = { body = body, } local function addObjectToWorld(objshape, vertices, userdata) local shape if objshape == "polyline" then shape = framework.newChainShape(false, unpack(vertices)) else shape = framework.newPolygonShape(unpack(vertices)) end local fixture = framework.newFixture(body, shape) fixture:setUserData(userdata) local obj = { shape = shape, fixture = fixture, } table.insert(collision, obj) end local function getPolygonVertices(object, tile, precalc) local ox, oy = 0, 0 if not precalc then ox = object.x oy = object.y end local vertices = {} for _, vertex in ipairs(object.polygon) do table.insert(vertices, tile.x + ox + vertex.x) table.insert(vertices, tile.y + oy + vertex.y) end return vertices end local function calculateObjectPosition(object, tile) local o = { shape = object.shape, x = object.x, y = object.y, w = object.width, h = object.height, polygon = object.polygon or object.polyline or object.ellipse or object.rectangle } local t = tile or { x=0, y=0 } local userdata = { object = object, instance = t, tile = t.gid and self.tiles[t.gid] } if o.shape == "rectangle" then o.r = object.rotation or 0 local cos = math.cos(math.rad(o.r)) local sin = math.sin(math.rad(o.r)) if object.gid then local tileset = self.tiles[object.gid].tileset local lid = object.gid - self.tilesets[tileset].firstgid local tile = {} -- This fixes a height issue o.y = o.y + self.tiles[object.gid].offset.y for _, t in ipairs(self.tilesets[tileset].tiles) do if t.id == lid then tile = t break end end if tile.objectGroup then for _, obj in ipairs(tile.objectGroup.objects) do -- Every object in the tile calculateObjectPosition(obj, object) end return else o.w = self.tiles[object.gid].width o.h = self.tiles[object.gid].height end end o.polygon = { { x=o.x, y=o.y }, { x=o.x + o.w, y=o.y }, { x=o.x + o.w, y=o.y + o.h }, { x=o.x, y=o.y + o.h }, } for _, vertex in ipairs(o.polygon) do if self.orientation == "isometric" then vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y) end vertex.x, vertex.y = rotateVertex(vertex, o.x, o.y, cos, sin) end local vertices = getPolygonVertices(o, t, true) addObjectToWorld(o.shape, vertices, userdata) elseif o.shape == "ellipse" then if not o.polygon then o.polygon = convertEllipseToPolygon(o.x, o.y, o.w, o.h) end local vertices = getPolygonVertices(o, t, true) local triangles = framework.triangulate(vertices) for _, triangle in ipairs(triangles) do addObjectToWorld(o.shape, triangle, userdata) end elseif o.shape == "polygon" then local precalc = false if not t.gid then precalc = true end local vertices = getPolygonVertices(o, t, precalc) local triangles = framework.triangulate(vertices) for _, triangle in ipairs(triangles) do addObjectToWorld(o.shape, triangle, userdata) end elseif o.shape == "polyline" then local precalc = false if not t.gid then precalc = true end local vertices = getPolygonVertices(o, t, precalc) addObjectToWorld(o.shape, vertices, userdata) end end for _, tileset in ipairs(self.tilesets) do for _, tile in ipairs(tileset.tiles) do local gid = tileset.firstgid + tile.id if tile.objectGroup then if self.tileInstances[gid] then for _, instance in ipairs(self.tileInstances[gid]) do for _, object in ipairs(tile.objectGroup.objects) do -- Every object in every instance of a tile calculateObjectPosition(object, instance) end end end elseif tile.properties.collidable == "true" and self.tileInstances[gid] then for _, instance in ipairs(self.tileInstances[gid]) do -- Every instance of a tile local object = { shape = "rectangle", x = 0, y = 0, width = tileset.tilewidth, height = tileset.tileheight, } calculateObjectPosition(object, instance) end end end end for _, layer in ipairs(self.layers) do if layer.properties.collidable == "true" then -- Entire layer if layer.type == "tilelayer" then for y, tiles in ipairs(layer.data) do for x, tile in pairs(tiles) do local object = { shape = "rectangle", x = x * self.tilewidth + tile.offset.x, y = y * self.tileheight + tile.offset.y, width = tile.width, height = tile.height, } calculateObjectPosition(object) 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, } calculateObjectPosition(object) end end if layer.type == "objectgroup" then for _, object in ipairs(layer.objects) do if object.properties.collidable == "true" then -- Individual objects calculateObjectPosition(object) end end end end return collision end function Map:setTiles(index, tileset, gid) local function getTiles(i, t, m, s) i = i - m local n = 0 while i >= t do i = i - t if n ~= 0 then i = i - s end if i >= 0 then n = n + 1 end end return n end local quad = framework.newQuad local mw = self.tilewidth local iw = tileset.imagewidth local ih = tileset.imageheight local tw = tileset.tilewidth local th = tileset.tileheight local s = tileset.spacing local m = tileset.margin local w = getTiles(iw, tw, m, s) local h = getTiles(ih, th, m, s) for y = 1, h do for x = 1, w do local id = gid - tileset.firstgid local qx = (x - 1) * tw + m + (x - 1) * s local qy = (y - 1) * th + m + (y - 1) * s local properties local terrain local animation for _, tile in pairs(tileset.tiles) do if tile.id == id then properties = tile.properties animation = tile.animation 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, quad = quad(qx, qy, tw, th, iw, ih), properties = properties, terrain = terrain, animation = animation, frame = 1, time = 0, width = tw, height = th, sx = 1, sy = 1, r = 0, offset = { x = -mw + tileset.tileoffset.x, y = -th + tileset.tileoffset.y, }, } if self.orientation == "isometric" then tile.offset.x = -mw / 2 end self.tiles[gid] = tile gid = gid + 1 end end return gid end function Map:setLayer(layer, path) layer.x = layer.x or 0 layer.y = layer.y or 0 layer.update = function(dt) return 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: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 image = formatPath(path..layer.image) layer.image = framework.newImage(image) layer.width = layer.image:getWidth() layer.height = layer.image:getHeight() end end self.layers[layer.name] = layer end function Map:setTileData(layer) local i = 1 local map = {} for y = 1, layer.height do map[y] = {} for x = 1, layer.width do local gid = layer.data[i] if gid > 0 then map[y][x] = self.tiles[gid] or self:setFlippedGID(gid) end i = i + 1 end end layer.data = map end function Map:setObjectCoordinates(layer) local function updateVertex(vertex, x, y, cos, sin) if self.orientation == "isometric" then x, y = self:convertIsometricToScreen(x, y) vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y) end return rotateVertex(vertex, x, y, cos, sin) end 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 r = object.rotation local cos = math.cos(math.rad(r)) local sin = math.sin(math.rad(r)) if object.shape == "rectangle" then object.rectangle = {} local vertices if object.gid then vertices = { { x=x, y=y - h }, { x=x + w, y=y - h }, { x=x + w, y=y }, { x=x, y=y }, } else vertices = { { x=x, y=y }, { x=x + w, y=y }, { x=x + w, y=y + h }, { x=x, y=y + h }, } end for _, vertex in ipairs(vertices) do vertex.x, vertex.y = updateVertex(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 = convertEllipseToPolygon(x, y, w, h) for _, vertex in ipairs(vertices) do vertex.x, vertex.y = updateVertex(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 = x + vertex.x vertex.y = y + vertex.y vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin) end elseif object.shape == "polyline" then for _, vertex in ipairs(object.polyline) do vertex.x = x + vertex.x vertex.y = y + vertex.y vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin) end end end end function Map:setSpriteBatches(layer) local newBatch = framework.newSpriteBatch local w = framework.getWidth() local h = framework.getHeight() local tw = self.tilewidth local th = self.tileheight local bw = math.ceil(w / tw) local bh = math.ceil(h / th) -- Minimum of 400 tiles per batch if bw < 20 then bw = 20 end if bh < 20 then bh = 20 end local size = bw * bh local batches = { width = bw, height = bh, data = {}, } for y = 1, layer.height do local by = math.ceil(y / bh) for x = 1, layer.width do local tile = layer.data[y][x] local bx = math.ceil(x / bw) local id if tile then local ts = tile.tileset local image = self.tilesets[tile.tileset].image batches.data[ts] = batches.data[ts] or {} batches.data[ts][by] = batches.data[ts][by] or {} batches.data[ts][by][bx] = batches.data[ts][by][bx] or newBatch(image, size) local batch = batches.data[ts][by][bx] local tx, ty, origx, origy if self.orientation == "orthogonal" then tx = x * tw + tile.offset.x ty = y * th + tile.offset.y origx = tx origy = ty -- Compensation for scale/rotation shift local compx = 0 local compy = 0 if tile.sx < 0 then compx = tw end if tile.sy < 0 then compy = th end if tile.r > 0 then tx = tx + th - compy ty = ty + th - tw + compx elseif tile.r < 0 then tx = tx + compy ty = ty + th - compx else tx = tx + compx ty = ty + compy end elseif self.orientation == "isometric" then tx = (x - y) * (tw / 2) + tile.offset.x + layer.width * tw / 2 ty = (x + y) * (th / 2) + tile.offset.y elseif self.orientation == "staggered" then if y % 2 == 0 then tx = x * tw + tw / 2 + tile.offset.x else tx = x * tw + tile.offset.x end ty = y * th / 2 + tile.offset.y + th / 2 end id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy) self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=origx or tx, y=origy or ty }) end end end layer.batches = batches end function Map:setObjectSpriteBatches(layer) local newBatch = framework.newSpriteBatch local tw = self.tilewidth local th = self.tileheight local batches = {} for _, object in ipairs(layer.objects) do if object.gid then local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid) local ts = tile.tileset local image = self.tilesets[tile.tileset].image batches[ts] = batches[ts] or newBatch(image, 100) local batch = batches[ts] local tx = object.x + tw + tile.offset.x local ty = object.y + tile.offset.y -- Compensation for scale/rotation shift if tile.sx < 0 then tx = tx + tw end if tile.sy < 0 then ty = ty + th end if tile.r > 0 then tx = tx + tw end if tile.r < 0 then ty = ty + th end id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy) self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=tx, y=ty }) end end layer.batches = batches end function Map:setDrawRange(tx, ty, w, h) tx = -tx ty = -ty local tw, th = self.tilewidth, self.tileheight local sx, sy, ex, ey if self.orientation == "orthogonal" then sx = math.ceil(tx / tw) sy = math.ceil(ty / th) ex = math.ceil(sx + w / tw) ey = math.ceil(sy + h / th) elseif self.orientation == "isometric" then sx = math.ceil(((ty / (th / 2)) + (tx / (tw / 2))) / 2) sy = math.ceil(((ty / (th / 2)) - (tx / (tw / 2))) / 2 - h / th) ex = math.ceil(sx + (h / th) + (w / tw)) ey = math.ceil(sy + (h / th) * 2 + (w / tw)) elseif self.orientation == "staggered" then sx = math.ceil(tx / tw - 1) sy = math.ceil(ty / th) ex = math.ceil(sx + w / tw + 1) ey = math.ceil(sy + h / th * 2) end self.drawRange = { sx = sx, sy = sy, ex = ex, ey = ey, } end function Map:addCustomLayer(name, index) local layer = { type = "customlayer", name = name, visible = true, opacity = 1, properties = {}, } function layer:draw() return end function layer:update(dt) return end table.insert(self.layers, index, layer) self.layers[name] = self.layers[index] return layer end 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.objects = nil layer.image = nil function layer:draw() return end function layer:update(dt) return end end function Map:removeLayer(index) local layer = assert(self.layers[index], "Layer not found: " .. index) if type(index) == "string" then for i, layer in ipairs(self.layers) do if layer.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 end function Map:update(dt) for gid, tile in pairs( self.tiles ) do local update local t if tile.animation then update = false tile.time = tile.time + dt * 1000 while tile.time > tonumber(tile.animation[tile.frame].duration) do 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 update = true end if update == true and self.tileInstances[gid] ~= nil then for _, j in pairs(self.tileInstances[gid]) do t = self.tiles[tile.animation[tile.frame].tileid + self.tilesets[tile.tileset].firstgid] j.batch:set( j.id, t.quad, j.x, j.y, 0 ) end end end end for _, layer in ipairs(self.layers) do layer:update(dt) end end -- modified function Map:draw() for _, layer in ipairs(self.layers) do if layer.visible and layer.opacity > 0 then self:drawLayer(layer) end end end function Map:drawLayer(layer) framework.setColor(255, 255, 255, 255 * layer.opacity) layer:draw() framework.setColor(255, 255, 255, 255) end 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") local bw = layer.batches.width local bh = layer.batches.height local sx = math.ceil((self.drawRange.sx - layer.x / self.tilewidth - 1) / bw) local sy = math.ceil((self.drawRange.sy - layer.y / self.tileheight - 1) / bh) local ex = math.ceil((self.drawRange.ex - layer.x / self.tilewidth + 1) / bw) local ey = math.ceil((self.drawRange.ey - layer.y / self.tileheight + 1) / bh) local mx = math.ceil(self.width / bw) local my = math.ceil(self.height / bh) for by=sy, ey do for bx=sx, ex do if bx >= 1 and bx <= mx and by >= 1 and by <= my then for _, batches in pairs(layer.batches.data) do local batch = batches[by] and batches[by][bx] if batch then framework.draw(batch, math.floor(layer.x), math.floor(layer.y)) end end end end end end 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.2 } local shadow = { 0, 0, 0, 255 * layer.opacity } local reset = { 255, 255, 255, 255 * layer.opacity } local function sortVertices(obj) local vertices = {{},{}} for _, vertex in ipairs(obj) do table.insert(vertices[1], vertex.x) table.insert(vertices[1], vertex.y) table.insert(vertices[2], vertex.x+1) table.insert(vertices[2], vertex.y+1) end return vertices end local function drawShape(obj, shape) local vertices = sortVertices(obj) if shape == "polyline" then framework.setColor(shadow) framework.line(vertices[2]) framework.setColor(line) framework.line(vertices[1]) return elseif shape == "polygon" then framework.setColor(fill) if not framework.isConvex(vertices[1]) then local triangles = framework.triangulate(vertices[1]) for _, triangle in ipairs(triangles) do framework.polygon("fill", triangle) end else framework.polygon("fill", vertices[1]) end else framework.setColor(fill) framework.polygon("fill", vertices[1]) end framework.setColor(shadow) framework.polygon("line", vertices[2]) framework.setColor(line) framework.polygon("line", vertices[1]) end for _, object in ipairs(layer.objects) do if object.shape == "rectangle" 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") end end framework.setColor(reset) for _, batch in pairs(layer.batches) do framework.draw(batch, 0, 0) end end 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 framework.draw(layer.image, layer.x, layer.y) end end function Map:drawWorldCollision(collision) for _, obj in ipairs(collision) do framework.polygon("line", collision.body:getWorldPoints(obj.shape:getPoints())) end end 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 = tile.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 function Map:getLayerProperties(layer) local l = self.layers[layer] if not l then return {} end return l.properties end function Map:getTileProperties(layer, x, y) local tile = self.layers[layer].data[y][x] if not tile then return {} end return tile.properties end 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 function Map:convertIsometricToScreen(x, y) local mw = self.width local tw, th = self.tilewidth, self.tileheight local ox = mw * tw / 2 local sx = (x - y) + ox local sy = (x + y) / 2 return sx, sy end function Map:convertScreenToIsometric(x, y) local mw, mh = self.width, self.height local tw, th = self.tilewidth, self.tileheight local ox = mw * tw / 2 local oy = mh * th / 2 local tx = (x / 2 + y) - ox / 2 local ty = (-x / 2 + y) + oy return tx, ty end function Map:convertTileToScreen(x, y) local tw, th = self.tilewidth, self.tileheight local sx = x * tw local sy = y * th return sx, sy end function Map:convertScreenToTile(x, y) local tw, th = self.tilewidth, self.tileheight local tx = x / tw local ty = y / th return tx, ty end function Map:convertIsometricTileToScreen(x, y) local mw = self.width local tw, th = self.tilewidth, self.tileheight local ox = mw * tw / 2 local sx = (x - y) * tw / 2 + ox local sy = (x + y) * th / 2 return sx, sy end function Map:convertScreenToIsometricTile(x, y) local mw = self.width local tw, th = self.tilewidth, self.tileheight local ox = mw * tw / 2 local tx = y / th + (x - ox) / tw local ty = y / th - (x - ox) / tw return tx, ty end function Map:convertStaggeredTileToScreen(x, y) local tw, th = self.tilewidth, self.tileheight local sx = x * tw + math.abs(math.ceil(y) % 2) * (tw / 2) - (math.ceil(y) % 2 * tw/2) local sy = y * (th / 2) + th/2 return sx, sy end function Map:convertScreenToStaggeredTile(x, y) local function topLeft(x, y) if (math.ceil(y) % 2) then return x, y - 1 else return x - 1, y - 1 end end local function topRight(x, y) if (math.ceil(y) % 2) then return x + 1, y - 1 else return x, y - 1 end end local function bottomLeft(x, y) if (math.ceil(y) % 2) then return x, y + 1 else return x - 1, y + 1 end end local function bottomRight(x, y) if (math.ceil(y) % 2) then return x + 1, y + 1 else return x, y + 1 end end local tw, th = self.tilewidth, self.tileheight local hh = th / 2 local ratio = th / tw local tx = x / tw local ty = y / th * 2 local ctx = math.ceil(x / tw) local cty = math.ceil(y / th) * 2 local rx = x - ctx * tw local ry = y - (cty / 2) * th if (hh - rx * ratio > ry) then return topLeft(tx, ty) elseif (-hh + rx * ratio > ry) then return topRight(tx, ty) elseif (hh + rx * ratio < ry) then return bottomLeft(tx, ty) elseif (hh * 3 - rx * ratio < ry) then return bottomRight(tx, ty) end return tx, ty end function Map.new(map) map = map .. ".lua" -- Get path to map local path = map:reverse():find("[/\\]") or "" if path ~= "" then path = map:sub(1, 1 + (#map - path)) end -- Load map map = framework.load(map) setfenv(map, {}) map = setmetatable(map(), {__index = Map}) map:init(path, framework) return map end return Map