Add working, minimally playable prototype.
This commit is contained in:
commit
11acc030e5
|
@ -0,0 +1,5 @@
|
|||
function love.conf(t)
|
||||
t.version = "11.3"
|
||||
t.window.fullscreen = true
|
||||
t.title = "Tortugger"
|
||||
end
|
|
@ -0,0 +1,337 @@
|
|||
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
|
||||
local inspect = {Options = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
inspect._VERSION = 'inspect.lua 3.1.0'
|
||||
inspect._URL = 'http://github.com/kikito/inspect.lua'
|
||||
inspect._DESCRIPTION = 'human-readable representations of tables'
|
||||
inspect._LICENSE = [[
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2022 Enrique García Cota
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end })
|
||||
inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end })
|
||||
|
||||
local tostring = tostring
|
||||
local rep = string.rep
|
||||
local match = string.match
|
||||
local char = string.char
|
||||
local gsub = string.gsub
|
||||
local fmt = string.format
|
||||
|
||||
local function rawpairs(t)
|
||||
return next, t, nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function smartQuote(str)
|
||||
if match(str, '"') and not match(str, "'") then
|
||||
return "'" .. str .. "'"
|
||||
end
|
||||
return '"' .. gsub(str, '"', '\\"') .. '"'
|
||||
end
|
||||
|
||||
|
||||
local shortControlCharEscapes = {
|
||||
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
|
||||
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127",
|
||||
}
|
||||
local longControlCharEscapes = { ["\127"] = "\127" }
|
||||
for i = 0, 31 do
|
||||
local ch = char(i)
|
||||
if not shortControlCharEscapes[ch] then
|
||||
shortControlCharEscapes[ch] = "\\" .. i
|
||||
longControlCharEscapes[ch] = fmt("\\%03d", i)
|
||||
end
|
||||
end
|
||||
|
||||
local function escape(str)
|
||||
return (gsub(gsub(gsub(str, "\\", "\\\\"),
|
||||
"(%c)%f[0-9]", longControlCharEscapes),
|
||||
"%c", shortControlCharEscapes))
|
||||
end
|
||||
|
||||
local function isIdentifier(str)
|
||||
return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$")
|
||||
end
|
||||
|
||||
local flr = math.floor
|
||||
local function isSequenceKey(k, sequenceLength)
|
||||
return type(k) == "number" and
|
||||
flr(k) == k and
|
||||
1 <= (k) and
|
||||
k <= sequenceLength
|
||||
end
|
||||
|
||||
local defaultTypeOrders = {
|
||||
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
|
||||
['function'] = 5, ['userdata'] = 6, ['thread'] = 7,
|
||||
}
|
||||
|
||||
local function sortKeys(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
|
||||
|
||||
if ta == tb and (ta == 'string' or ta == 'number') then
|
||||
return (a) < (b)
|
||||
end
|
||||
|
||||
local dta = defaultTypeOrders[ta] or 100
|
||||
local dtb = defaultTypeOrders[tb] or 100
|
||||
|
||||
|
||||
return dta == dtb and ta < tb or dta < dtb
|
||||
end
|
||||
|
||||
local function getKeys(t)
|
||||
|
||||
local seqLen = 1
|
||||
while rawget(t, seqLen) ~= nil do
|
||||
seqLen = seqLen + 1
|
||||
end
|
||||
seqLen = seqLen - 1
|
||||
|
||||
local keys, keysLen = {}, 0
|
||||
for k in rawpairs(t) do
|
||||
if not isSequenceKey(k, seqLen) then
|
||||
keysLen = keysLen + 1
|
||||
keys[keysLen] = k
|
||||
end
|
||||
end
|
||||
table.sort(keys, sortKeys)
|
||||
return keys, keysLen, seqLen
|
||||
end
|
||||
|
||||
local function countCycles(x, cycles)
|
||||
if type(x) == "table" then
|
||||
if cycles[x] then
|
||||
cycles[x] = cycles[x] + 1
|
||||
else
|
||||
cycles[x] = 1
|
||||
for k, v in rawpairs(x) do
|
||||
countCycles(k, cycles)
|
||||
countCycles(v, cycles)
|
||||
end
|
||||
countCycles(getmetatable(x), cycles)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function makePath(path, a, b)
|
||||
local newPath = {}
|
||||
local len = #path
|
||||
for i = 1, len do newPath[i] = path[i] end
|
||||
|
||||
newPath[len + 1] = a
|
||||
newPath[len + 2] = b
|
||||
|
||||
return newPath
|
||||
end
|
||||
|
||||
|
||||
local function processRecursive(process,
|
||||
item,
|
||||
path,
|
||||
visited)
|
||||
if item == nil then return nil end
|
||||
if visited[item] then return visited[item] end
|
||||
|
||||
local processed = process(item, path)
|
||||
if type(processed) == "table" then
|
||||
local processedCopy = {}
|
||||
visited[item] = processedCopy
|
||||
local processedKey
|
||||
|
||||
for k, v in rawpairs(processed) do
|
||||
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
|
||||
if processedKey ~= nil then
|
||||
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
|
||||
end
|
||||
end
|
||||
|
||||
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
|
||||
if type(mt) ~= 'table' then mt = nil end
|
||||
setmetatable(processedCopy, mt)
|
||||
processed = processedCopy
|
||||
end
|
||||
return processed
|
||||
end
|
||||
|
||||
local function puts(buf, str)
|
||||
buf.n = buf.n + 1
|
||||
buf[buf.n] = str
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Inspector = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local Inspector_mt = { __index = Inspector }
|
||||
|
||||
local function tabify(inspector)
|
||||
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
|
||||
end
|
||||
|
||||
function Inspector:getId(v)
|
||||
local id = self.ids[v]
|
||||
local ids = self.ids
|
||||
if not id then
|
||||
local tv = type(v)
|
||||
id = (ids[tv] or 0) + 1
|
||||
ids[v], ids[tv] = id, id
|
||||
end
|
||||
return tostring(id)
|
||||
end
|
||||
|
||||
function Inspector:putValue(v)
|
||||
local buf = self.buf
|
||||
local tv = type(v)
|
||||
if tv == 'string' then
|
||||
puts(buf, smartQuote(escape(v)))
|
||||
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
|
||||
tv == 'cdata' or tv == 'ctype' then
|
||||
puts(buf, tostring(v))
|
||||
elseif tv == 'table' and not self.ids[v] then
|
||||
local t = v
|
||||
|
||||
if t == inspect.KEY or t == inspect.METATABLE then
|
||||
puts(buf, tostring(t))
|
||||
elseif self.level >= self.depth then
|
||||
puts(buf, '{...}')
|
||||
else
|
||||
if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end
|
||||
|
||||
local keys, keysLen, seqLen = getKeys(t)
|
||||
|
||||
puts(buf, '{')
|
||||
self.level = self.level + 1
|
||||
|
||||
for i = 1, seqLen + keysLen do
|
||||
if i > 1 then puts(buf, ',') end
|
||||
if i <= seqLen then
|
||||
puts(buf, ' ')
|
||||
self:putValue(t[i])
|
||||
else
|
||||
local k = keys[i - seqLen]
|
||||
tabify(self)
|
||||
if isIdentifier(k) then
|
||||
puts(buf, k)
|
||||
else
|
||||
puts(buf, "[")
|
||||
self:putValue(k)
|
||||
puts(buf, "]")
|
||||
end
|
||||
puts(buf, ' = ')
|
||||
self:putValue(t[k])
|
||||
end
|
||||
end
|
||||
|
||||
local mt = getmetatable(t)
|
||||
if type(mt) == 'table' then
|
||||
if seqLen + keysLen > 0 then puts(buf, ',') end
|
||||
tabify(self)
|
||||
puts(buf, '<metatable> = ')
|
||||
self:putValue(mt)
|
||||
end
|
||||
|
||||
self.level = self.level - 1
|
||||
|
||||
if keysLen > 0 or type(mt) == 'table' then
|
||||
tabify(self)
|
||||
elseif seqLen > 0 then
|
||||
puts(buf, ' ')
|
||||
end
|
||||
|
||||
puts(buf, '}')
|
||||
end
|
||||
|
||||
else
|
||||
puts(buf, fmt('<%s %d>', tv, self:getId(v)))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function inspect.inspect(root, options)
|
||||
options = options or {}
|
||||
|
||||
local depth = options.depth or (math.huge)
|
||||
local newline = options.newline or '\n'
|
||||
local indent = options.indent or ' '
|
||||
local process = options.process
|
||||
|
||||
if process then
|
||||
root = processRecursive(process, root, {}, {})
|
||||
end
|
||||
|
||||
local cycles = {}
|
||||
countCycles(root, cycles)
|
||||
|
||||
local inspector = setmetatable({
|
||||
buf = { n = 0 },
|
||||
ids = {},
|
||||
cycles = cycles,
|
||||
depth = depth,
|
||||
level = 0,
|
||||
newline = newline,
|
||||
indent = indent,
|
||||
}, Inspector_mt)
|
||||
|
||||
inspector:putValue(root)
|
||||
|
||||
return table.concat(inspector.buf)
|
||||
end
|
||||
|
||||
setmetatable(inspect, {
|
||||
__call = function(_, root, options)
|
||||
return inspect.inspect(root, options)
|
||||
end,
|
||||
})
|
||||
|
||||
return inspect
|
|
@ -0,0 +1,230 @@
|
|||
local sti = require("sti")
|
||||
local inspect = require("inspect")
|
||||
|
||||
love.graphics.setDefaultFilter("nearest", "nearest")
|
||||
|
||||
local game = {
|
||||
SCREEN_WIDTH=320,
|
||||
SCREEN_HEIGHT=200,
|
||||
TILE_SIZE=16,
|
||||
-- NOTE: playfield dimensions in tiles
|
||||
PLAYFIELD_OFFSET_TOP=1.5,
|
||||
PLAYFIELD_WIDTH=20,
|
||||
PLAYFIELD_HEIGHT=10,
|
||||
VEHICLE_SPEED=10,
|
||||
FLOATDEV_SPEED=8,
|
||||
playFieldTiles={},
|
||||
map=sti("map.lua"),
|
||||
entities={
|
||||
vehicles={},
|
||||
floatdevs={},
|
||||
},
|
||||
hazards={},
|
||||
drawGrid=true,
|
||||
}
|
||||
|
||||
function t2p(tileDim)
|
||||
return tileDim*game.TILE_SIZE
|
||||
end
|
||||
|
||||
function tilePos2Pixels(tilePos)
|
||||
return t2p(tilePos.x - 1), t2p(tilePos.y - 1)
|
||||
end
|
||||
|
||||
function screenPixelToTilePos(pixelPos)
|
||||
return {
|
||||
x=math.floor(pixelPos.x/game.TILE_SIZE + 1),
|
||||
y=math.floor((pixelPos.y + t2p(game.PLAYFIELD_OFFSET_TOP))/game.TILE_SIZE)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local player = {
|
||||
pos={x=t2p(0), y=t2p(0)},
|
||||
turtlePos={x=1, y=2},
|
||||
turtleHit=false,
|
||||
lives=3,
|
||||
turtles=3,
|
||||
dead=false,
|
||||
deadTimer=2,
|
||||
}
|
||||
|
||||
function resetPlayer()
|
||||
player.pos = {x=t2p(0), y=t2p(0)}
|
||||
player.dead = false
|
||||
end
|
||||
|
||||
function love.load()
|
||||
local desktop_width, desktop_height = love.window.getDesktopDimensions()
|
||||
game.scaleW = desktop_width / game.SCREEN_WIDTH
|
||||
game.scaleH = desktop_height / game.SCREEN_HEIGHT
|
||||
for i, layer in ipairs(game.map.layers) do
|
||||
if layer.objects then
|
||||
for j, mapObj in ipairs(layer.objects) do
|
||||
mapObj.visible = false
|
||||
local typekey = mapObj.type.."s"
|
||||
if game.entities[typekey] == nil then
|
||||
game.entities[typekey] = {}
|
||||
end
|
||||
table.insert(
|
||||
game.entities[typekey],
|
||||
{
|
||||
x=mapObj.x,
|
||||
y=mapObj.y,
|
||||
w=mapObj.width,
|
||||
h=mapObj.height,
|
||||
type=mapObj.type,
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function love.keyreleased(key)
|
||||
if player.dead then
|
||||
return
|
||||
end
|
||||
local newPos = screenPixelToTilePos(player.pos)
|
||||
local oldPos = screenPixelToTilePos(player.pos)
|
||||
if key == "up" or key == "w" then
|
||||
newPos.y = newPos.y - 1
|
||||
end
|
||||
if key == "down" or key == "s" then
|
||||
newPos.y = newPos.y + 1
|
||||
end
|
||||
if key == "right" or key == "d" then
|
||||
newPos.x = newPos.x + 1
|
||||
end
|
||||
if key == "left" or key == "a" then
|
||||
newPos.x = newPos.x - 1
|
||||
end
|
||||
player.turtlePos = oldPos
|
||||
if newPos.x < 1 then
|
||||
newPos.x = 1
|
||||
end
|
||||
if newPos.x > game.PLAYFIELD_WIDTH then
|
||||
newPos.x = game.PLAYFIELD_WIDTH
|
||||
end
|
||||
if newPos.y < 1 then
|
||||
newPos.y = 1
|
||||
end
|
||||
if newPos.y > game.PLAYFIELD_HEIGHT then
|
||||
newPos.y = game.PLAYFIELD_HEIGHT
|
||||
end
|
||||
player.pos.x = t2p(newPos.x - 1)
|
||||
player.pos.y = t2p(newPos.y - 1)
|
||||
end
|
||||
|
||||
function tilePos2Rect(tilePos)
|
||||
return {
|
||||
x=t2p(tilePos.x - 1),
|
||||
y=t2p(tilePos.y - 1),
|
||||
w=game.TILE_SIZE,
|
||||
h=game.TILE_SIZE,
|
||||
}
|
||||
end
|
||||
|
||||
function rectanglesIntersect(rect1, rect2)
|
||||
return (rect1.x < rect2.x + rect2.w and
|
||||
rect1.x + rect1.w > rect2.x and
|
||||
rect1.y < rect2.y + rect2.h and
|
||||
rect1.y + rect1.h > rect2.y)
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
player.turtleHit = false
|
||||
if player.dead then
|
||||
player.deadTimer = player.deadTimer - dt
|
||||
if player.deadTimer <= 0 then
|
||||
player.deadTimer = 2
|
||||
resetPlayer()
|
||||
end
|
||||
else
|
||||
local playerRect = tilePos2Rect(screenPixelToTilePos(player.pos))
|
||||
local turtleRect = tilePos2Rect(player.turtlePos)
|
||||
if player.floating then
|
||||
player.pos.y = player.pos.y + game.FLOATDEV_SPEED*dt
|
||||
end
|
||||
for i, vehicle in ipairs(game.entities.vehicles) do
|
||||
if rectanglesIntersect(playerRect, vehicle) then
|
||||
player.dead = true
|
||||
player.lives = player.lives - 1
|
||||
end
|
||||
if rectanglesIntersect(turtleRect, vehicle) then
|
||||
player.turtleHit = true
|
||||
end
|
||||
|
||||
vehicle.y = vehicle.y + game.VEHICLE_SPEED*dt
|
||||
if vehicle.y > t2p(game.PLAYFIELD_HEIGHT) then
|
||||
vehicle.y = -vehicle.h
|
||||
end
|
||||
end
|
||||
player.floating = false
|
||||
for i, floatdev in ipairs(game.entities.floatdevs) do
|
||||
if not player.floating and rectanglesIntersect(playerRect, floatdev) then
|
||||
player.floating = true
|
||||
end
|
||||
floatdev.y = floatdev.y + game.FLOATDEV_SPEED*dt
|
||||
if floatdev.y > t2p(game.PLAYFIELD_HEIGHT) then
|
||||
floatdev.y = -floatdev.h
|
||||
end
|
||||
end
|
||||
if not player.floating then
|
||||
for i, water in ipairs(game.entities.waters) do
|
||||
if not player.dead and rectanglesIntersect(playerRect, water) then
|
||||
player.dead = true
|
||||
player.lives = player.lives - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.setColor(1, 1, 1)
|
||||
game.map:draw(0, t2p(game.PLAYFIELD_OFFSET_TOP), game.scaleW, game.scaleH)
|
||||
love.graphics.push()
|
||||
-- <playfield>
|
||||
love.graphics.scale(game.scaleW, game.scaleH)
|
||||
love.graphics.translate(0, t2p(game.PLAYFIELD_OFFSET_TOP))
|
||||
if game.drawGrid then
|
||||
love.graphics.setColor(0.4, 0.3, 0.1)
|
||||
for i=0, game.PLAYFIELD_WIDTH do
|
||||
love.graphics.line(t2p(i), 0, t2p(i), t2p(game.PLAYFIELD_HEIGHT))
|
||||
end
|
||||
for i=0, game.PLAYFIELD_HEIGHT do
|
||||
love.graphics.line(0, t2p(i), t2p(game.PLAYFIELD_WIDTH), t2p(i))
|
||||
end
|
||||
end
|
||||
love.graphics.setColor(0.9, 0.2, 0)
|
||||
for i, vehicle in ipairs(game.entities.vehicles) do
|
||||
love.graphics.rectangle("fill", vehicle.x, vehicle.y, vehicle.w, vehicle.h)
|
||||
end
|
||||
love.graphics.setColor(0.8, 0.7, 0)
|
||||
for i, floatdev in ipairs(game.entities.floatdevs) do
|
||||
love.graphics.rectangle("fill", floatdev.x, floatdev.y, floatdev.w, floatdev.h)
|
||||
end
|
||||
if player.dead then
|
||||
love.graphics.setColor(0.8, 0.1, 0)
|
||||
else
|
||||
love.graphics.setColor(0.9, 0.6, 0.1)
|
||||
end
|
||||
-- TODO: change this to use player's stored pixel pos
|
||||
local x, y = player.pos.x, player.pos.y
|
||||
love.graphics.circle("fill", x + game.TILE_SIZE/2, y + game.TILE_SIZE/2, game.TILE_SIZE/2)
|
||||
love.graphics.setColor(0.2, 0.1, 0.1)
|
||||
love.graphics.circle("line", x + game.TILE_SIZE/2, y + game.TILE_SIZE/2, game.TILE_SIZE/2)
|
||||
x, y = tilePos2Pixels(player.turtlePos)
|
||||
love.graphics.setColor(0.2, 0.1, 0.1)
|
||||
love.graphics.circle("line", x + game.TILE_SIZE/2, y + game.TILE_SIZE/2, game.TILE_SIZE/2)
|
||||
if player.turtleHit then
|
||||
love.graphics.setColor(0.8, 0.1, 0)
|
||||
else
|
||||
love.graphics.setColor(0, 0.7, 0.2)
|
||||
end
|
||||
love.graphics.circle("fill", x + game.TILE_SIZE/2, y + game.TILE_SIZE/2, game.TILE_SIZE/2)
|
||||
-- </playfield>
|
||||
love.graphics.pop()
|
||||
end
|
|
@ -0,0 +1,248 @@
|
|||
return {
|
||||
version = "1.5",
|
||||
luaversion = "5.1",
|
||||
tiledversion = "1.8.6",
|
||||
orientation = "orthogonal",
|
||||
renderorder = "right-down",
|
||||
width = 20,
|
||||
height = 10,
|
||||
tilewidth = 16,
|
||||
tileheight = 16,
|
||||
nextlayerid = 4,
|
||||
nextobjectid = 12,
|
||||
properties = {},
|
||||
tilesets = {
|
||||
{
|
||||
name = "tiles",
|
||||
firstgid = 1,
|
||||
tilewidth = 16,
|
||||
tileheight = 16,
|
||||
spacing = 0,
|
||||
margin = 0,
|
||||
columns = 10,
|
||||
image = "tiles.png",
|
||||
imagewidth = 160,
|
||||
imageheight = 16,
|
||||
transparentcolor = "#ff00ff",
|
||||
objectalignment = "unspecified",
|
||||
tileoffset = {
|
||||
x = 0,
|
||||
y = 0
|
||||
},
|
||||
grid = {
|
||||
orientation = "orthogonal",
|
||||
width = 16,
|
||||
height = 16
|
||||
},
|
||||
properties = {},
|
||||
wangsets = {},
|
||||
tilecount = 10,
|
||||
tiles = {}
|
||||
}
|
||||
},
|
||||
layers = {
|
||||
{
|
||||
type = "tilelayer",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 20,
|
||||
height = 10,
|
||||
id = 1,
|
||||
name = "Capa de patrones 1",
|
||||
visible = true,
|
||||
opacity = 1,
|
||||
offsetx = 0,
|
||||
offsety = 0,
|
||||
parallaxx = 1,
|
||||
parallaxy = 1,
|
||||
properties = {},
|
||||
encoding = "lua",
|
||||
data = {
|
||||
1, 2, 3, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 3, 7, 3, 4, 6,
|
||||
1, 2, 7, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 7, 7, 7, 4, 6,
|
||||
1, 2, 3, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 3, 7, 3, 4, 6,
|
||||
1, 2, 7, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 7, 7, 7, 4, 6,
|
||||
1, 2, 3, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 3, 7, 3, 4, 6,
|
||||
1, 2, 7, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 7, 7, 7, 4, 6,
|
||||
1, 2, 3, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 3, 7, 3, 4, 6,
|
||||
1, 2, 7, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 7, 7, 7, 4, 6,
|
||||
1, 2, 3, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 3, 7, 3, 4, 6,
|
||||
1, 2, 7, 4, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 2, 7, 7, 7, 4, 6
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "objectgroup",
|
||||
draworder = "topdown",
|
||||
id = 3,
|
||||
name = "hazards",
|
||||
visible = true,
|
||||
opacity = 1,
|
||||
offsetx = 0,
|
||||
offsety = 0,
|
||||
parallaxx = 1,
|
||||
parallaxy = 1,
|
||||
properties = {},
|
||||
objects = {
|
||||
{
|
||||
id = 4,
|
||||
name = "",
|
||||
type = "water",
|
||||
shape = "rectangle",
|
||||
x = 80,
|
||||
y = -0.25,
|
||||
width = 48,
|
||||
height = 160,
|
||||
rotation = 0,
|
||||
visible = false,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 5,
|
||||
name = "",
|
||||
type = "water",
|
||||
shape = "rectangle",
|
||||
x = 144,
|
||||
y = 0,
|
||||
width = 64,
|
||||
height = 160,
|
||||
rotation = 0,
|
||||
visible = false,
|
||||
properties = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "objectgroup",
|
||||
draworder = "topdown",
|
||||
id = 2,
|
||||
name = "entities",
|
||||
visible = true,
|
||||
opacity = 1,
|
||||
offsetx = 0,
|
||||
offsety = 0,
|
||||
parallaxx = 1,
|
||||
parallaxy = 1,
|
||||
tintcolor = { 170, 0, 0 },
|
||||
properties = {},
|
||||
objects = {
|
||||
{
|
||||
id = 1,
|
||||
name = "",
|
||||
type = "vehicle",
|
||||
shape = "rectangle",
|
||||
x = 18,
|
||||
y = 0,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 8,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 82,
|
||||
y = 0,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 11,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 98,
|
||||
y = 32,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 2,
|
||||
name = "",
|
||||
type = "vehicle",
|
||||
shape = "rectangle",
|
||||
x = 18,
|
||||
y = 64,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 7,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 82,
|
||||
y = 64,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 10,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 98,
|
||||
y = 98,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 3,
|
||||
name = "",
|
||||
type = "vehicle",
|
||||
shape = "rectangle",
|
||||
x = 18,
|
||||
y = 128,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 6,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 82,
|
||||
y = 128,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
},
|
||||
{
|
||||
id = 9,
|
||||
name = "",
|
||||
type = "floatdev",
|
||||
shape = "rectangle",
|
||||
x = 98,
|
||||
y = 160,
|
||||
width = 12,
|
||||
height = 16,
|
||||
rotation = 0,
|
||||
visible = true,
|
||||
properties = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.8" tiledversion="1.8.6" orientation="orthogonal" renderorder="right-down" width="20" height="10" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="12">
|
||||
<editorsettings>
|
||||
<export target="map.lua" format="lua"/>
|
||||
</editorsettings>
|
||||
<tileset firstgid="1" name="tiles" tilewidth="16" tileheight="16" tilecount="10" columns="10">
|
||||
<image source="tiles.png" trans="ff00ff" width="160" height="16"/>
|
||||
</tileset>
|
||||
<layer id="1" name="Capa de patrones 1" width="20" height="10">
|
||||
<data encoding="csv">
|
||||
1,2,3,4,1,5,5,5,1,5,5,5,5,1,2,3,7,3,4,6,
|
||||
1,2,7,4,1,5,5,5,1,5,5,5,5,1,2,7,7,7,4,6,
|
||||
1,2,3,4,1,5,5,5,1,5,5,5,5,1,2,3,7,3,4,6,
|
||||
1,2,7,4,1,5,5,5,1,5,5,5,5,1,2,7,7,7,4,6,
|
||||
1,2,3,4,1,5,5,5,1,5,5,5,5,1,2,3,7,3,4,6,
|
||||
1,2,7,4,1,5,5,5,1,5,5,5,5,1,2,7,7,7,4,6,
|
||||
1,2,3,4,1,5,5,5,1,5,5,5,5,1,2,3,7,3,4,6,
|
||||
1,2,7,4,1,5,5,5,1,5,5,5,5,1,2,7,7,7,4,6,
|
||||
1,2,3,4,1,5,5,5,1,5,5,5,5,1,2,3,7,3,4,6,
|
||||
1,2,7,4,1,5,5,5,1,5,5,5,5,1,2,7,7,7,4,6
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="3" name="hazards">
|
||||
<object id="4" type="water" x="80" y="-0.25" width="48" height="160"/>
|
||||
<object id="5" type="water" x="144" y="0" width="64" height="160"/>
|
||||
</objectgroup>
|
||||
<objectgroup id="2" name="entities" tintcolor="#aa0000">
|
||||
<object id="1" type="vehicle" x="18" y="0" width="12" height="16"/>
|
||||
<object id="8" type="floatdev" x="82" y="0" width="12" height="16"/>
|
||||
<object id="11" type="floatdev" x="98" y="32" width="12" height="16"/>
|
||||
<object id="2" type="vehicle" x="18" y="64" width="12" height="16"/>
|
||||
<object id="7" type="floatdev" x="82" y="64" width="12" height="16"/>
|
||||
<object id="10" type="floatdev" x="98" y="98" width="12" height="16"/>
|
||||
<object id="3" type="vehicle" x="18" y="128" width="12" height="16"/>
|
||||
<object id="6" type="floatdev" x="82" y="128" width="12" height="16"/>
|
||||
<object id="9" type="floatdev" x="98" y="160" width="12" height="16"/>
|
||||
</objectgroup>
|
||||
</map>
|
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue