tiny-ecs/lib/sti.lua
2015-04-27 14:12:11 +08:00

1264 lines
29 KiB
Lua

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