Update for Spring 2019 Game Jam

This commit is contained in:
Alexander Griffith 2019-04-20 20:38:53 -04:00
parent 05a2b28752
commit ff33a7538e
14 changed files with 964 additions and 4956 deletions

View file

@ -1,4 +0,0 @@
* 2018-10-16 Alexander Griffith <griffitaj@gmail.com>
- Updated love version in love-release.sh to 11.1
- Added libs fennelview, lume & stdio

View file

@ -6,5 +6,5 @@ love.conf = function(t)
t.window.width = 720
t.window.height = 450
t.window.vsync = false
t.version = "11.1"
t.version = "11.2"
end

View file

@ -1,773 +0,0 @@
local bump = {
_VERSION = 'bump v3.1.7',
_URL = 'https://github.com/kikito/bump.lua',
_DESCRIPTION = 'A collision detection library for Lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 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.
]]
}
------------------------------------------
-- Auxiliary functions
------------------------------------------
local DELTA = 1e-10 -- floating-point margin of error
local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
local function sign(x)
if x > 0 then return 1 end
if x == 0 then return 0 end
return -1
end
local function nearest(x, a, b)
if abs(a - x) < abs(b - x) then return a else return b end
end
local function assertType(desiredType, value, name)
if type(value) ~= desiredType then
error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
end
end
local function assertIsPositiveNumber(value, name)
if type(value) ~= 'number' or value <= 0 then
error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
end
end
local function assertIsRect(x,y,w,h)
assertType('number', x, 'x')
assertType('number', y, 'y')
assertIsPositiveNumber(w, 'w')
assertIsPositiveNumber(h, 'h')
end
local defaultFilter = function()
return 'slide'
end
------------------------------------------
-- Rectangle functions
------------------------------------------
local function rect_getNearestCorner(x,y,w,h, px, py)
return nearest(px, x, x+w), nearest(py, y, y+h)
end
-- This is a generalized implementation of the liang-barsky algorithm, which also returns
-- the normals of the sides where the segment intersects.
-- Returns nil if the segment never touches the rect
-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
ti1, ti2 = ti1 or 0, ti2 or 1
local dx, dy = x2-x1, y2-y1
local nx, ny
local nx1, ny1, nx2, ny2 = 0,0,0,0
local p, q, r
for side = 1,4 do
if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
end
if p == 0 then
if q <= 0 then return nil end
else
r = q / p
if p < 0 then
if r > ti2 then return nil
elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
end
else -- p > 0
if r < ti1 then return nil
elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
end
end
end
end
return ti1,ti2, nx1,ny1, nx2,ny2
end
-- Calculates the minkowsky difference between 2 rects, which is another rect
local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
return x2 - x1 - w1,
y2 - y1 - h1,
w1 + w2,
h1 + h2
end
local function rect_containsPoint(x,y,w,h, px,py)
return px - x > DELTA and py - y > DELTA and
x + w - px > DELTA and y + h - py > DELTA
end
local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and x2 < x1+w1 and
y1 < y2+h2 and y2 < y1+h1
end
local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
local dx = x1 - x2 + (w1 - w2)/2
local dy = y1 - y2 + (h1 - h2)/2
return dx*dx + dy*dy
end
local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
goalX = goalX or x1
goalY = goalY or y1
local dx, dy = goalX - x1, goalY - y1
local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
local overlaps, ti, nx, ny
if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
ti = -wi * hi -- ti is the negative area of intersection
overlaps = true
else
local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
-- item tunnels into other
if ti1
and ti1 < 1
and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner
and (0 < ti1 + DELTA
or 0 == ti1 and ti2 > 0)
then
ti, nx, ny = ti1, nx1, ny1
overlaps = false
end
end
if not ti then return end
local tx, ty
if overlaps then
if dx == 0 and dy == 0 then
-- intersecting and not moving - use minimum displacement vector
local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
if abs(px) < abs(py) then py = 0 else px = 0 end
nx, ny = sign(px), sign(py)
tx, ty = x1 + px, y1 + py
else
-- intersecting and moving - move in the opposite direction
local ti1, _
ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
if not ti1 then return end
tx, ty = x1 + dx * ti1, y1 + dy * ti1
end
else -- tunnel
tx, ty = x1 + dx * ti, y1 + dy * ti
end
return {
overlaps = overlaps,
ti = ti,
move = {x = dx, y = dy},
normal = {x = nx, y = ny},
touch = {x = tx, y = ty},
itemRect = {x = x1, y = y1, w = w1, h = h1},
otherRect = {x = x2, y = y2, w = w2, h = h2}
}
end
------------------------------------------
-- Grid functions
------------------------------------------
local function grid_toWorld(cellSize, cx, cy)
return (cx - 1)*cellSize, (cy-1)*cellSize
end
local function grid_toCell(cellSize, x, y)
return floor(x / cellSize) + 1, floor(y / cellSize) + 1
end
-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
-- It has been modified to include both cells when the ray "touches a grid corner",
-- and with a different exit condition
local function grid_traverse_initStep(cellSize, ct, t1, t2)
local v = t2 - t1
if v > 0 then
return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
elseif v < 0 then
return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
else
return 0, math.huge, math.huge
end
end
local function grid_traverse(cellSize, x1,y1,x2,y2, f)
local cx1,cy1 = grid_toCell(cellSize, x1,y1)
local cx2,cy2 = grid_toCell(cellSize, x2,y2)
local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
local cx,cy = cx1,cy1
f(cx, cy)
-- The default implementation had an infinite loop problem when
-- approaching the last cell in some occassions. We finish iterating
-- when we are *next* to the last cell
while abs(cx - cx2) + abs(cy - cy2) > 1 do
if tx < ty then
tx, cx = tx + dx, cx + stepX
f(cx, cy)
else
-- Addition: include both cells when going through corners
if tx == ty then f(cx + stepX, cy) end
ty, cy = ty + dy, cy + stepY
f(cx, cy)
end
end
-- If we have not arrived to the last cell, use it
if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
end
local function grid_toCellRect(cellSize, x,y,w,h)
local cx,cy = grid_toCell(cellSize, x, y)
local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
return cx, cy, cr - cx + 1, cb - cy + 1
end
------------------------------------------
-- Responses
------------------------------------------
local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
return col.touch.x, col.touch.y, {}, 0
end
local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
if move.x ~= 0 or move.y ~= 0 then
if col.normal.x ~= 0 then
goalX = tch.x
else
goalY = tch.y
end
end
col.slide = {x = goalX, y = goalY}
x,y = tch.x, tch.y
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
local tx, ty = tch.x, tch.y
local bx, by = tx, ty
if move.x ~= 0 or move.y ~= 0 then
local bnx, bny = goalX - tx, goalY - ty
if col.normal.x == 0 then bny = -bny else bnx = -bnx end
bx, by = tx + bnx, ty + bny
end
col.bounce = {x = bx, y = by}
x,y = tch.x, tch.y
goalX, goalY = bx, by
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
------------------------------------------
-- World
------------------------------------------
local World = {}
local World_mt = {__index = World}
-- Private functions and methods
local function sortByWeight(a,b) return a.weight < b.weight end
local function sortByTiAndDistance(a,b)
if a.ti == b.ti then
local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
return ad < bd
end
return a.ti < b.ti
end
local function addItemToCell(self, item, cx, cy)
self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
local row = self.rows[cy]
row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
local cell = row[cx]
self.nonEmptyCells[cell] = true
if not cell.items[item] then
cell.items[item] = true
cell.itemCount = cell.itemCount + 1
end
end
local function removeItemFromCell(self, item, cx, cy)
local row = self.rows[cy]
if not row or not row[cx] or not row[cx].items[item] then return false end
local cell = row[cx]
cell.items[item] = nil
cell.itemCount = cell.itemCount - 1
if cell.itemCount == 0 then
self.nonEmptyCells[cell] = nil
end
return true
end
local function getDictItemsInCellRect(self, cl,ct,cw,ch)
local items_dict = {}
for cy=ct,ct+ch-1 do
local row = self.rows[cy]
if row then
for cx=cl,cl+cw-1 do
local cell = row[cx]
if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
for item,_ in pairs(cell.items) do
items_dict[item] = true
end
end
end
end
end
return items_dict
end
local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cells, cellsLen, visited = {}, 0, {}
grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
local row = self.rows[cy]
if not row then return end
local cell = row[cx]
if not cell or visited[cell] then return end
visited[cell] = true
cellsLen = cellsLen + 1
cells[cellsLen] = cell
end)
return cells, cellsLen
end
local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
local visited, itemInfo, itemInfoLen = {},{},0
for i=1,len do
cell = cells[i]
for item in pairs(cell.items) do
if not visited[item] then
visited[item] = true
if (not filter or filter(item)) then
rect = self.rects[item]
l,t,w,h = rect.x,rect.y,rect.w,rect.h
ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
-- the sorting is according to the t of an infinite line, not the segment
tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
itemInfoLen = itemInfoLen + 1
itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
end
end
end
end
end
table.sort(itemInfo, sortByWeight)
return itemInfo, itemInfoLen
end
local function getResponseByName(self, name)
local response = self.responses[name]
if not response then
error(('Unknown collision type: %s (%s)'):format(name, type(name)))
end
return response
end
-- Misc Public Methods
function World:addResponse(name, response)
self.responses[name] = response
end
function World:project(item, x,y,w,h, goalX, goalY, filter)
assertIsRect(x,y,w,h)
goalX = goalX or x
goalY = goalY or y
filter = filter or defaultFilter
local collisions, len = {}, 0
local visited = {}
if item ~= nil then visited[item] = true end
-- This could probably be done with less cells using a polygon raster over the cells instead of a
-- bounding rect of the whole movement. Conditional to building a queryPolygon method
local tl, tt = min(goalX, x), min(goalY, y)
local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
local tw, th = tr-tl, tb-tt
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
for other,_ in pairs(dictItemsInCellRect) do
if not visited[other] then
visited[other] = true
local responseName = filter(item, other)
if responseName then
local ox,oy,ow,oh = self:getRect(other)
local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
if col then
col.other = other
col.item = item
col.type = responseName
len = len + 1
collisions[len] = col
end
end
end
end
table.sort(collisions, sortByTiAndDistance)
return collisions, len
end
function World:countCells()
local count = 0
for _,row in pairs(self.rows) do
for _,_ in pairs(row) do
count = count + 1
end
end
return count
end
function World:hasItem(item)
return not not self.rects[item]
end
function World:getItems()
local items, len = {}, 0
for item,_ in pairs(self.rects) do
len = len + 1
items[len] = item
end
return items, len
end
function World:countItems()
local len = 0
for _ in pairs(self.rects) do len = len + 1 end
return len
end
function World:getRect(item)
local rect = self.rects[item]
if not rect then
error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
end
return rect.x, rect.y, rect.w, rect.h
end
function World:toWorld(cx, cy)
return grid_toWorld(self.cellSize, cx, cy)
end
function World:toCell(x,y)
return grid_toCell(self.cellSize, x, y)
end
--- Query methods
function World:queryRect(x,y,w,h, filter)
assertIsRect(x,y,w,h)
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:queryPoint(x,y, filter)
local cx,cy = self:toCell(x,y)
local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:querySegment(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local items = {}
for i=1, len do
items[i] = itemInfo[i].item
end
return items, len
end
function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local dx, dy = x2-x1, y2-y1
local info, ti1, ti2
for i=1, len do
info = itemInfo[i]
ti1 = info.ti1
ti2 = info.ti2
info.weight = nil
info.x1 = x1 + dx * ti1
info.y1 = y1 + dy * ti1
info.x2 = x1 + dx * ti2
info.y2 = y1 + dy * ti2
end
return itemInfo, len
end
--- Main methods
function World:add(item, x,y,w,h)
local rect = self.rects[item]
if rect then
error('Item ' .. tostring(item) .. ' added to the world twice.')
end
assertIsRect(x,y,w,h)
self.rects[item] = {x=x,y=y,w=w,h=h}
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
addItemToCell(self, item, cx, cy)
end
end
return item
end
function World:remove(item)
local x,y,w,h = self:getRect(item)
self.rects[item] = nil
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
removeItemFromCell(self, item, cx, cy)
end
end
end
function World:update(item, x2,y2,w2,h2)
local x1,y1,w1,h1 = self:getRect(item)
w2,h2 = w2 or w1, h2 or h1
assertIsRect(x2,y2,w2,h2)
if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
local cellSize = self.cellSize
local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
local cyOut
for cy = ct1, cb1 do
cyOut = cy < ct2 or cy > cb2
for cx = cl1, cr1 do
if cyOut or cx < cl2 or cx > cr2 then
removeItemFromCell(self, item, cx, cy)
end
end
end
for cy = ct2, cb2 do
cyOut = cy < ct1 or cy > cb1
for cx = cl2, cr2 do
if cyOut or cx < cl1 or cx > cr1 then
addItemToCell(self, item, cx, cy)
end
end
end
end
local rect = self.rects[item]
rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
end
end
function World:move(item, goalX, goalY, filter)
local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
self:update(item, actualX, actualY)
return actualX, actualY, cols, len
end
function World:check(item, goalX, goalY, filter)
filter = filter or defaultFilter
local visited = {[item] = true}
local visitedFilter = function(itm, other)
if visited[other] then return false end
return filter(itm, other)
end
local cols, len = {}, 0
local x,y,w,h = self:getRect(item)
local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
while projected_len > 0 do
local col = projected_cols[1]
len = len + 1
cols[len] = col
visited[col.other] = true
local response = getResponseByName(self, col.type)
goalX, goalY, projected_cols, projected_len = response(
self,
col,
x, y, w, h,
goalX, goalY,
visitedFilter
)
end
return goalX, goalY, cols, len
end
-- Public library functions
bump.newWorld = function(cellSize)
cellSize = cellSize or 64
assertIsPositiveNumber(cellSize, 'cellSize')
local world = setmetatable({
cellSize = cellSize,
rects = {},
rows = {},
nonEmptyCells = {},
responses = {}
}, World_mt)
world:addResponse('touch', touch)
world:addResponse('cross', cross)
world:addResponse('slide', slide)
world:addResponse('bounce', bounce)
return world
end
bump.rect = {
getNearestCorner = rect_getNearestCorner,
getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
getDiff = rect_getDiff,
containsPoint = rect_containsPoint,
isIntersecting = rect_isIntersecting,
getSquareDistance = rect_getSquareDistance,
detectCollision = rect_detectCollision
}
bump.responses = {
touch = touch,
cross = cross,
slide = slide,
bounce = bounce
}
return bump

View file

@ -3,55 +3,132 @@
local fennel_dir = arg[0]:match("(.-)[^\\/]+$")
package.path = fennel_dir .. "?.lua;" .. package.path
local fennel = require('fennel')
local unpack = unpack or table.unpack
local help = [[
Usage: fennel [FLAG] [FILE]
Run fennel, a lisp programming language for the Lua runtime.
--repl : Launch an interactive repl session
--compile FILES : Compile files and write their Lua to stdout
--help : Display this text
When not given a flag, runs the file given as the first argument.]]
--no-searcher : Skip installing package.searchers entry
--indent VAL : Indent compiler output with VAL
--add-package-path PATH : Add PATH to package.path for finding Lua modules
--add-fennel-path PATH : Add PATH to fennel.path for finding Fennel modules
--globals G1[,G2...] : Allow these globals in addition to standard ones
--globals-only G1[,G2] : Same as above, but exclude standard ones
--help : Display this text
--version : Show version
When not given a flag, runs the file given as the first argument.
When given neither flag nor file, launches a repl.
If ~/.fennelrc exists, loads it before launching a repl.]]
local options = {
sourcemap = true,
sourcemap = true
}
local function dosafe(filename, opts, arg1)
local function dosafe(filename, opts, args)
local ok, val = xpcall(function()
return fennel.dofile(filename, opts, arg1)
return fennel.dofile(filename, opts, unpack(args))
end, fennel.traceback)
if not ok then
print(val)
io.stderr:write(val .. "\n")
os.exit(1)
end
return val
end
local compileOptHandlers = {
['--indent'] = function ()
options.indent = table.remove(arg, 3)
local function allowGlobals(globalNames)
options.allowedGlobals = {}
for g in globalNames:gmatch("([^,]+),?") do
table.insert(options.allowedGlobals, g)
end
end
for i=#arg, 1, -1 do
if arg[i] == "--no-searcher" then
options.no_searcher = true
table.remove(arg, i)
elseif arg[i] == "--indent" then
options.indent = table.remove(arg, i+1)
if options.indent == "false" then options.indent = false end
table.remove(arg, 2)
end,
['--sourcemap'] = function ()
options.sourcemap = table.remove(arg, 3)
table.remove(arg, i)
elseif arg[i] == "--add-package-path" then
local entry = table.remove(arg, i+1)
package.path = entry .. ";" .. package.path
table.remove(arg, i)
elseif arg[i] == "--add-fennel-path" then
local entry = table.remove(arg, i+1)
fennel.path = entry .. ";" .. fennel.path
table.remove(arg, i)
elseif arg[i] == "--sourcemap" then
options.sourcemap = table.remove(arg, i+1)
if options.sourcemap == "false" then options.sourcemap = false end
table.remove(arg, 2)
end,
['--correlate'] = function ()
options.correlate = true
table.remove(arg, 2)
end,
}
table.remove(arg, i)
elseif arg[i] == "--globals" then
allowGlobals(table.remove(arg, i+1))
for globalName in pairs(_G) do
table.insert(options.allowedGlobals, globalName)
end
table.remove(arg, i)
elseif arg[i] == "--globals-only" then
allowGlobals(table.remove(arg, i+1))
table.remove(arg, i)
end
end
if not options.no_searcher then
table.insert((package.loaders or package.searchers),
fennel.make_searcher({correlate = true}))
end
-- Try to load readline library
local function tryReadline(opts)
local ok, readline = pcall(require, "readline")
if ok then
readline.set_options({
keeplines = 1000
})
function opts.readChunk(parserState)
local prompt = parserState.stackSize > 0 and '.. ' or '>> '
local str = readline.readline(prompt)
if str then
return str .. "\n"
end
end
-- completer is registered by the repl, until then returns empty list
local completer
function opts.registerCompleter(replCompleter)
completer = replCompleter
end
local function replCompleter(text, from, to)
if completer then return completer(text, from, to) else return {} end
end
readline.set_complete_function(replCompleter)
end
end
if arg[1] == "--repl" or #arg == 0 then
local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options)
if ppok then
options.pp = pp
else
ppok, pp = pcall(require, "fennelview")
if ppok then
options.pp = pp
end
end
local initFilename = (os.getenv("HOME") or "") .. "/.fennelrc"
local init = io.open(initFilename, "rb")
tryReadline(options)
if init then
init:close()
-- pass in options so fennerlrc can make changes to it
@ -60,23 +137,25 @@ if arg[1] == "--repl" or #arg == 0 then
print("Welcome to fennel!")
fennel.repl(options)
elseif arg[1] == "--compile" then
-- Handle options
while compileOptHandlers[arg[2]] do
compileOptHandlers[arg[2]]()
end
for i = 2, #arg do
local f = assert(io.open(arg[i], "rb"))
local f = arg[i] == "-" and io.stdin or assert(io.open(arg[i], "rb"))
options.filename=arg[i]
local ok, val = xpcall(function()
return fennel.compileString(f:read("*all"), options)
end, fennel.traceback)
if ok then
print(val)
if not ok then os.exit(1) end
else
io.stderr:write(val .. "\n")
os.exit(1)
end
f:close()
end
elseif #arg >= 1 and arg[1] ~= "--help" then
elseif arg[1] == "--version" or arg[1] == "-v" then
print("Fennel " .. fennel.version)
elseif #arg >= 1 and arg[1] ~= "--help" and arg[1] ~= "-h" then
local filename = table.remove(arg, 1) -- let the script have remaining args
dosafe(filename)
dosafe(filename, nil, arg)
else
print(help)
end

File diff suppressed because it is too large Load diff

View file

@ -1,132 +1,105 @@
local function _0_(str)
local function view_quote(str)
return ("\"" .. str:gsub("\"", "\\\"") .. "\"")
end
local quote = _0_
local short_control_char_escapes = ({["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"})
local function _1_()
local long = ({})
local short_control_char_escapes = {["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"}
local long_control_char_esapes
do
local long = {}
for i = 0, 31 do
local ch = string.char(i)
local function _2_()
if not short_control_char_escapes[ch] then
short_control_char_escapes[ch] = ("\\" .. i)
long[ch] = ("\\%03d"):format(i)
return nil
end
end
_2_()
long_control_char_esapes = long
end
return long
end
local long_control_char_esapes = _1_()
local function _2_(str)
local function escape(str)
local str = str:gsub("\\", "\\\\")
local str = str:gsub("(%c)%f[0-9]", long_control_char_esapes)
return str:gsub("%c", short_control_char_escapes)
end
local escape = _2_
local function _3_(k, len)
return ((type(k)) == ("number") and (1) <= (k) and (k) <= (len) and (math.floor(k)) == (k))
local function sequence_key_3f(k, len)
return ((type(k) == "number") and (1 <= k) and (k <= len) and (math.floor(k) == k))
end
local sequence_key_3f = _3_
local type_order = ({["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6})
local function _4_(a, b)
local type_order = {["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6}
local function sort_keys(a, b)
local ta = type(a)
local tb = type(b)
if ((ta) == (tb) and (ta) ~= ("boolean") and ((ta) == ("string") or (ta) == ("number"))) then
return (a) < (b)
if ((ta == tb) and (ta ~= "boolean") and ((ta == "string") or (ta == "number"))) then
return (a < b)
else
local dta = type_order[a]
local dtb = type_order[b]
if (dta and dtb) then
return (dta) < (dtb)
return (dta < dtb)
elseif dta then
return true
elseif dtb then
return false
elseif "else" then
return (ta) < (tb)
return (ta < tb)
end
end
end
local sort_keys = _4_
local function _5_(t)
local function get_sequence_length(t)
local len = 1
for i in ipairs(t) do
len = i
end
return len
end
local get_sequence_length = _5_
local function _6_(t)
local keys = ({})
local function get_nonsequential_keys(t)
local keys = {}
local sequence_length = get_sequence_length(t)
for k in pairs(t) do
local function _7_()
if not sequence_key_3f(k, sequence_length) then
return table.insert(keys, k)
table.insert(keys, k)
end
end
_7_()
end
table.sort(keys, sort_keys)
return keys, sequence_length
end
local get_nonsequential_keys = _6_
local function recur(t, appearances)
local function _7_()
if (type(t)) == ("table") then
local function count_table_appearances(t, appearances)
if (type(t) == "table") then
if not appearances[t] then
appearances[t] = 1
for k, v in pairs(t) do
recur(k, appearances)
recur(v, appearances)
count_table_appearances(k, appearances)
count_table_appearances(v, appearances)
end
return nil
end
else
if (t and (t) == (t)) then
if (t and (t == t)) then
appearances[t] = ((appearances[t] or 0) + 1)
return nil
end
end
end
_7_()
return appearances
end
local count_table_appearances = recur
local put_value = nil
local function _7_(self, ...)
for _, v in ipairs(({...})) do
local function puts(self, ...)
for _, v in ipairs({...}) do
table.insert(self.buffer, v)
end
return nil
end
local puts = _7_
local function _8_(self)
local function tabify(self)
return puts(self, "\n", self.indent:rep(self.level))
end
local tabify = _8_
local function _9_(self, v)
return (self.ids[v]) ~= (nil)
local function already_visited_3f(self, v)
return (self.ids[v] ~= nil)
end
local already_visited_3f = _9_
local function _10_(self, v)
local function get_id(self, v)
local id = self.ids[v]
local function _11_()
if not id then
local tv = type(v)
id = ((self["max-ids"][tv] or 0) + 1)
self["max-ids"][tv] = id
self.ids[v] = id
return nil
end
end
_11_()
return tostring(id)
end
local get_id = _10_
local function _11_(self, t, length)
local function put_sequential_table(self, t, length)
puts(self, "[")
self.level = (self.level + 1)
for i = 1, length do
@ -136,16 +109,14 @@ local function _11_(self, t, length)
self.level = (self.level - 1)
return puts(self, " ]")
end
local put_sequential_table = _11_
local function _12_(self, k)
if ((type(k)) == ("string") and k:find("^[%w?\\^_`!#$%&*+-./@~:|<=>]+$")) then
local function put_key(self, k)
if ((type(k) == "string") and k:find("^[-%w?\\^_`!#$%&*+./@~:|<=>]+$")) then
return puts(self, ":", k)
else
return put_value(self, k)
end
end
local put_key = _12_
local function _13_(self, t)
local function put_kv_table(self, t)
puts(self, "{")
self.level = (self.level + 1)
for k, v in pairs(t) do
@ -158,44 +129,42 @@ local function _13_(self, t)
tabify(self)
return puts(self, "}")
end
local put_kv_table = _13_
local function _14_(self, t)
local function put_table(self, t)
if already_visited_3f(self, t) then
return puts(self, "#<table ", get_id(self, t), ">")
elseif (self.level) >= (self.depth) then
elseif (self.level >= self.depth) then
return puts(self, "{...}")
elseif "else" then
local non_seq_keys, length = get_nonsequential_keys(t)
local id = get_id(self, t)
if (self.appearances[t]) > (1) then
if (self.appearances[t] > 1) then
return puts(self, "#<", id, ">")
elseif ((#non_seq_keys) == (0) and (#t) == (0)) then
elseif ((#non_seq_keys == 0) and (#t == 0)) then
return puts(self, "{}")
elseif (#non_seq_keys) == (0) then
elseif (#non_seq_keys == 0) then
return put_sequential_table(self, t, length)
elseif "else" then
return put_kv_table(self, t)
end
end
end
local put_table = _14_
local function _15_(self, v)
local function _0_(self, v)
local tv = type(v)
if (tv) == ("string") then
return puts(self, quote(escape(v)))
elseif ((tv) == ("number") or (tv) == ("boolean") or (tv) == ("nil")) then
if (tv == "string") then
return puts(self, view_quote(escape(v)))
elseif ((tv == "number") or (tv == "boolean") or (tv == "nil")) then
return puts(self, tostring(v))
elseif (tv) == ("table") then
elseif (tv == "table") then
return put_table(self, v)
elseif "else" then
return puts(self, "#<", tv, " ", get_id(self, v), ">")
return puts(self, "#<", tostring(v), ">")
end
end
put_value = _15_
local function _16_(root, options)
local options = (options or ({}))
local inspector = ({["max-ids"] = ({}), appearances = count_table_appearances(root, ({})), buffer = ({}), depth = (options.depth or 128), ids = ({}), indent = (options.indent or " "), level = 0})
put_value = _0_
local function fennelview(root, options)
local options = (options or {})
local inspector = {["max-ids"] = {}, appearances = count_table_appearances(root, {}), buffer = {}, depth = (options.depth or 128), ids = {}, indent = (options.indent or " "), level = 0}
put_value(inspector, root)
return table.concat(inspector.buffer)
end
return _16_
return fennelview

View file

@ -1,15 +0,0 @@
-- https://love2d.org/wiki/General_math
return function(l1p1x,l1p1y, l1p2x,l1p2y, l2p1x,l2p1y, l2p2x,l2p2y)
local a1,b1,a2,b2 = l1p2y-l1p1y, l1p1x-l1p2x, l2p2y-l2p1y, l2p1x-l2p2x
local c1,c2 = a1*l1p1x+b1*l1p1y, a2*l2p1x+b2*l2p1y
local det,x,y = a1*b2 - a2*b1
if det==0 then return false, "The lines are parallel." end
x,y = (b2*c1-b1*c2)/det, (a1*c2-a2*c1)/det
if not (math.min(l1p1x,l1p2x) <= x and x <= math.max(l1p1x,l1p2x) and
math.min(l1p1y,l1p2y) <= y and y <= math.max(l1p1y,l1p2y)) or
not (math.min(l2p1x,l2p2x) <= x and x <= math.max(l2p1x,l2p2x) and
math.min(l2p1y,l2p2y) <= y and y <= math.max(l2p1y,l2p2y)) then
return false, "The lines don't intersect."
end
return x,y
end

View file

@ -1,20 +1,34 @@
--
-- lume
--
-- Copyright (c) 2015 rxi
-- Copyright (c) 2018 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
-- 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.
--
local lume = { _version = "2.2.3" }
local lume = { _version = "2.3.0" }
local pairs, ipairs = pairs, ipairs
local type, assert, unpack = type, assert, unpack or table.unpack
local tostring, tonumber = tostring, tonumber
local math_floor = math.floor
local math_ceil = math.ceil
local math_random = math.random
local math_atan2 = math.atan2 or math.atan
local math_sqrt = math.sqrt
local math_abs = math.abs
@ -111,15 +125,20 @@ function lume.angle(x1, y1, x2, y2)
end
function lume.vector(angle, magnitude)
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
end
function lume.random(a, b)
if not a then a, b = 0, 1 end
if not b then b = 0 end
return a + math_random() * (b - a)
return a + math.random() * (b - a)
end
function lume.randomchoice(t)
return t[math_random(#t)]
return t[math.random(#t)]
end
@ -139,7 +158,7 @@ end
function lume.isarray(x)
return (type(x) == "table" and x[1] ~= nil) and true or false
return type(x) == "table" and x[1] ~= nil
end
@ -194,7 +213,7 @@ end
function lume.shuffle(t)
local rtn = {}
for i = 1, #t do
local r = math_random(i)
local r = math.random(i)
if r ~= i then
rtn[i] = rtn[r]
end
@ -283,7 +302,7 @@ function lume.reduce(t, fn, first)
end
function lume.set(t)
function lume.unique(t)
local rtn = {}
for k in pairs(lume.invert(t)) do
rtn[#rtn + 1] = k
@ -658,7 +677,7 @@ end
function lume.uuid()
local fn = function(x)
local r = math_random(16) - 1
local r = math.random(16) - 1
r = (x == "x") and (r + 1) or (r % 4) + 9
return ("0123456789abcdef"):sub(r, r)
end
@ -705,7 +724,9 @@ end
local ripairs_iter = function(t, i)
i = i - 1
local v = t[i]
if v then return i, v end
if v ~= nil then
return i, v
end
end
function lume.ripairs(t)
@ -735,15 +756,6 @@ function lume.color(str, mul)
end
function lume.rgba(color)
local a = math_floor((color / 16777216) % 256)
local r = math_floor((color / 65536) % 256)
local g = math_floor((color / 256) % 256)
local b = math_floor((color) % 256)
return r, g, b, a
end
local chain_mt = {}
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
function(fn)

File diff suppressed because it is too large Load diff

View file

@ -1,126 +0,0 @@
local lg = 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.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

View file

@ -1,286 +0,0 @@
--- Box2D plugin for STI
-- @module box2d
-- @author Landon Manning
-- @copyright 2017
-- @license MIT/X11
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.6",
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 fixture = love.physics.newFixture(body, shape)
fixture:setUserData(userdata)
if userdata.properties.sensor == true then
fixture:setSensor(true)
end
local obj = {
object = object,
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
}
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))
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 = tileset.tileheight
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
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.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 = {collision.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

View file

@ -1,206 +0,0 @@
--- Bump.lua plugin for STI
-- @module bump.lua
-- @author David Serrano (BobbyJones|FrenchFryLord)
-- @copyright 2016
-- @license MIT/X11
local lg = require((...):gsub('plugins.bump', 'graphics'))
local wrappers = {}
local bump_world = nil
return {
bump_LICENSE = "MIT/X11",
bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
bump_VERSION = "3.1.6.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 = {}
bump_world = world
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 = {
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
}
wrappers[object] = t
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,
properties = tile.properties
}
wrappers[tile] = t
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 = {
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
}
wrappers[object] = t
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,
properties = tile.properties
}
wrappers[tile] = t
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 = {
x = obj.x + map.offsetx,
y = obj.y + map.offsety,
width = obj.width,
height = obj.height,
layer = layer,
properties = obj.properties
}
-- PNH: what the hell?
if obj.gid then
t.y = t.y - obj.height
end
wrappers[obj] = t
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_collidables = collidables
end,
--- Remove layer
-- @param index to layer to be removed
-- @param world bump world the holds the tiles
-- @param tx Translate on X
-- @param ty Translate on Y
-- @param sx Scale on X
-- @param sy Scale on Y
bump_removeLayer = function(map, index, world)
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
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, world, 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))
for _, collidable in pairs(map.bump_collidables) do
lg.rectangle("line", world:getRect(collidable))
end
lg.pop()
end,
-- PNH: we added this so we could work around STI shenanigans and actually
-- perform operations on the world directly. STI wraps each object in a
-- table before adding it to the bump world, so we look up the object in
-- the wrapper mapping before handing off to bump.
bump_wrap = function(method, object, ...)
return bump_world[method](bump_world, wrappers[object], ...)
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

View file

@ -1,208 +0,0 @@
-- 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,'/')
until k == 0
repeat -- A/../ -> (empty)
path,k = path:gsub(np_pat1,'')
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 origx = tileX
local origy = tileY
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)
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
end
--- Project isometric position to cartesian position
function utils.convert_isometric_to_screen(map, x, y)
local mapH = map.height
local tileW = map.tilewidth
local tileH = map.tileheight
local tileX = x / tileH
local tileY = y / tileH
local offsetX = mapH * 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
return utils