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.width = 720
t.window.height = 450 t.window.height = 450
t.window.vsync = false t.window.vsync = false
t.version = "11.1" t.version = "11.2"
end 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("(.-)[^\\/]+$") local fennel_dir = arg[0]:match("(.-)[^\\/]+$")
package.path = fennel_dir .. "?.lua;" .. package.path package.path = fennel_dir .. "?.lua;" .. package.path
local fennel = require('fennel') local fennel = require('fennel')
local unpack = unpack or table.unpack
local help = [[ local help = [[
Usage: fennel [FLAG] [FILE] Usage: fennel [FLAG] [FILE]
Run fennel, a lisp programming language for the Lua runtime.
--repl : Launch an interactive repl session --repl : Launch an interactive repl session
--compile FILES : Compile files and write their Lua to stdout --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 = { local options = {
sourcemap = true, sourcemap = true
} }
local function dosafe(filename, opts, arg1) local function dosafe(filename, opts, args)
local ok, val = xpcall(function() local ok, val = xpcall(function()
return fennel.dofile(filename, opts, arg1) return fennel.dofile(filename, opts, unpack(args))
end, fennel.traceback) end, fennel.traceback)
if not ok then if not ok then
print(val) io.stderr:write(val .. "\n")
os.exit(1) os.exit(1)
end end
return val return val
end end
local compileOptHandlers = { local function allowGlobals(globalNames)
['--indent'] = function () options.allowedGlobals = {}
options.indent = table.remove(arg, 3) 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 if options.indent == "false" then options.indent = false end
table.remove(arg, 2) table.remove(arg, i)
end, elseif arg[i] == "--add-package-path" then
['--sourcemap'] = function () local entry = table.remove(arg, i+1)
options.sourcemap = table.remove(arg, 3) 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 if options.sourcemap == "false" then options.sourcemap = false end
table.remove(arg, 2) table.remove(arg, i)
end, elseif arg[i] == "--globals" then
['--correlate'] = function () allowGlobals(table.remove(arg, i+1))
options.correlate = true for globalName in pairs(_G) do
table.remove(arg, 2) table.insert(options.allowedGlobals, globalName)
end, 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 if arg[1] == "--repl" or #arg == 0 then
local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options) local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options)
if ppok then if ppok then
options.pp = pp options.pp = pp
else
ppok, pp = pcall(require, "fennelview")
if ppok then
options.pp = pp
end
end end
local initFilename = (os.getenv("HOME") or "") .. "/.fennelrc" local initFilename = (os.getenv("HOME") or "") .. "/.fennelrc"
local init = io.open(initFilename, "rb") local init = io.open(initFilename, "rb")
tryReadline(options)
if init then if init then
init:close() init:close()
-- pass in options so fennerlrc can make changes to it -- 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!") print("Welcome to fennel!")
fennel.repl(options) fennel.repl(options)
elseif arg[1] == "--compile" then elseif arg[1] == "--compile" then
-- Handle options
while compileOptHandlers[arg[2]] do
compileOptHandlers[arg[2]]()
end
for i = 2, #arg do 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] options.filename=arg[i]
local ok, val = xpcall(function() local ok, val = xpcall(function()
return fennel.compileString(f:read("*all"), options) return fennel.compileString(f:read("*all"), options)
end, fennel.traceback) end, fennel.traceback)
if ok then
print(val) print(val)
if not ok then os.exit(1) end else
io.stderr:write(val .. "\n")
os.exit(1)
end
f:close() f:close()
end 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 local filename = table.remove(arg, 1) -- let the script have remaining args
dosafe(filename) dosafe(filename, nil, arg)
else else
print(help) print(help)
end 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("\"", "\\\"") .. "\"") return ("\"" .. str:gsub("\"", "\\\"") .. "\"")
end end
local quote = _0_ local short_control_char_escapes = {["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"}
local short_control_char_escapes = ({["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"}) local long_control_char_esapes
local function _1_() do
local long = ({}) local long = {}
for i = 0, 31 do for i = 0, 31 do
local ch = string.char(i) local ch = string.char(i)
local function _2_()
if not short_control_char_escapes[ch] then if not short_control_char_escapes[ch] then
short_control_char_escapes[ch] = ("\\" .. i) short_control_char_escapes[ch] = ("\\" .. i)
long[ch] = ("\\%03d"):format(i) long[ch] = ("\\%03d"):format(i)
return nil
end end
end end
_2_() long_control_char_esapes = long
end end
return long local function escape(str)
end
local long_control_char_esapes = _1_()
local function _2_(str)
local str = str:gsub("\\", "\\\\") local str = str:gsub("\\", "\\\\")
local str = str:gsub("(%c)%f[0-9]", long_control_char_esapes) local str = str:gsub("(%c)%f[0-9]", long_control_char_esapes)
return str:gsub("%c", short_control_char_escapes) return str:gsub("%c", short_control_char_escapes)
end end
local escape = _2_ local function sequence_key_3f(k, len)
local function _3_(k, len) return ((type(k) == "number") and (1 <= k) and (k <= len) and (math.floor(k) == k))
return ((type(k)) == ("number") and (1) <= (k) and (k) <= (len) and (math.floor(k)) == (k))
end end
local sequence_key_3f = _3_ local type_order = {["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6}
local type_order = ({["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6}) local function sort_keys(a, b)
local function _4_(a, b)
local ta = type(a) local ta = type(a)
local tb = type(b) local tb = type(b)
if ((ta) == (tb) and (ta) ~= ("boolean") and ((ta) == ("string") or (ta) == ("number"))) then if ((ta == tb) and (ta ~= "boolean") and ((ta == "string") or (ta == "number"))) then
return (a) < (b) return (a < b)
else else
local dta = type_order[a] local dta = type_order[a]
local dtb = type_order[b] local dtb = type_order[b]
if (dta and dtb) then if (dta and dtb) then
return (dta) < (dtb) return (dta < dtb)
elseif dta then elseif dta then
return true return true
elseif dtb then elseif dtb then
return false return false
elseif "else" then elseif "else" then
return (ta) < (tb) return (ta < tb)
end end
end end
end end
local sort_keys = _4_ local function get_sequence_length(t)
local function _5_(t)
local len = 1 local len = 1
for i in ipairs(t) do for i in ipairs(t) do
len = i len = i
end end
return len return len
end end
local get_sequence_length = _5_ local function get_nonsequential_keys(t)
local function _6_(t) local keys = {}
local keys = ({})
local sequence_length = get_sequence_length(t) local sequence_length = get_sequence_length(t)
for k in pairs(t) do for k in pairs(t) do
local function _7_()
if not sequence_key_3f(k, sequence_length) then if not sequence_key_3f(k, sequence_length) then
return table.insert(keys, k) table.insert(keys, k)
end end
end end
_7_()
end
table.sort(keys, sort_keys) table.sort(keys, sort_keys)
return keys, sequence_length return keys, sequence_length
end end
local get_nonsequential_keys = _6_ local function count_table_appearances(t, appearances)
local function recur(t, appearances) if (type(t) == "table") then
local function _7_()
if (type(t)) == ("table") then
if not appearances[t] then if not appearances[t] then
appearances[t] = 1 appearances[t] = 1
for k, v in pairs(t) do for k, v in pairs(t) do
recur(k, appearances) count_table_appearances(k, appearances)
recur(v, appearances) count_table_appearances(v, appearances)
end end
return nil
end end
else else
if (t and (t) == (t)) then if (t and (t == t)) then
appearances[t] = ((appearances[t] or 0) + 1) appearances[t] = ((appearances[t] or 0) + 1)
return nil
end end
end end
end
_7_()
return appearances return appearances
end end
local count_table_appearances = recur
local put_value = nil local put_value = nil
local function _7_(self, ...) local function puts(self, ...)
for _, v in ipairs(({...})) do for _, v in ipairs({...}) do
table.insert(self.buffer, v) table.insert(self.buffer, v)
end end
return nil return nil
end end
local puts = _7_ local function tabify(self)
local function _8_(self)
return puts(self, "\n", self.indent:rep(self.level)) return puts(self, "\n", self.indent:rep(self.level))
end end
local tabify = _8_ local function already_visited_3f(self, v)
local function _9_(self, v) return (self.ids[v] ~= nil)
return (self.ids[v]) ~= (nil)
end end
local already_visited_3f = _9_ local function get_id(self, v)
local function _10_(self, v)
local id = self.ids[v] local id = self.ids[v]
local function _11_()
if not id then if not id then
local tv = type(v) local tv = type(v)
id = ((self["max-ids"][tv] or 0) + 1) id = ((self["max-ids"][tv] or 0) + 1)
self["max-ids"][tv] = id self["max-ids"][tv] = id
self.ids[v] = id self.ids[v] = id
return nil
end end
end
_11_()
return tostring(id) return tostring(id)
end end
local get_id = _10_ local function put_sequential_table(self, t, length)
local function _11_(self, t, length)
puts(self, "[") puts(self, "[")
self.level = (self.level + 1) self.level = (self.level + 1)
for i = 1, length do for i = 1, length do
@ -136,16 +109,14 @@ local function _11_(self, t, length)
self.level = (self.level - 1) self.level = (self.level - 1)
return puts(self, " ]") return puts(self, " ]")
end end
local put_sequential_table = _11_ local function put_key(self, k)
local function _12_(self, k) if ((type(k) == "string") and k:find("^[-%w?\\^_`!#$%&*+./@~:|<=>]+$")) then
if ((type(k)) == ("string") and k:find("^[%w?\\^_`!#$%&*+-./@~:|<=>]+$")) then
return puts(self, ":", k) return puts(self, ":", k)
else else
return put_value(self, k) return put_value(self, k)
end end
end end
local put_key = _12_ local function put_kv_table(self, t)
local function _13_(self, t)
puts(self, "{") puts(self, "{")
self.level = (self.level + 1) self.level = (self.level + 1)
for k, v in pairs(t) do for k, v in pairs(t) do
@ -158,44 +129,42 @@ local function _13_(self, t)
tabify(self) tabify(self)
return puts(self, "}") return puts(self, "}")
end end
local put_kv_table = _13_ local function put_table(self, t)
local function _14_(self, t)
if already_visited_3f(self, t) then if already_visited_3f(self, t) then
return puts(self, "#<table ", get_id(self, t), ">") return puts(self, "#<table ", get_id(self, t), ">")
elseif (self.level) >= (self.depth) then elseif (self.level >= self.depth) then
return puts(self, "{...}") return puts(self, "{...}")
elseif "else" then elseif "else" then
local non_seq_keys, length = get_nonsequential_keys(t) local non_seq_keys, length = get_nonsequential_keys(t)
local id = get_id(self, t) local id = get_id(self, t)
if (self.appearances[t]) > (1) then if (self.appearances[t] > 1) then
return puts(self, "#<", id, ">") 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, "{}") return puts(self, "{}")
elseif (#non_seq_keys) == (0) then elseif (#non_seq_keys == 0) then
return put_sequential_table(self, t, length) return put_sequential_table(self, t, length)
elseif "else" then elseif "else" then
return put_kv_table(self, t) return put_kv_table(self, t)
end end
end end
end end
local put_table = _14_ local function _0_(self, v)
local function _15_(self, v)
local tv = type(v) local tv = type(v)
if (tv) == ("string") then if (tv == "string") then
return puts(self, quote(escape(v))) return puts(self, view_quote(escape(v)))
elseif ((tv) == ("number") or (tv) == ("boolean") or (tv) == ("nil")) then elseif ((tv == "number") or (tv == "boolean") or (tv == "nil")) then
return puts(self, tostring(v)) return puts(self, tostring(v))
elseif (tv) == ("table") then elseif (tv == "table") then
return put_table(self, v) return put_table(self, v)
elseif "else" then elseif "else" then
return puts(self, "#<", tv, " ", get_id(self, v), ">") return puts(self, "#<", tostring(v), ">")
end end
end end
put_value = _15_ put_value = _0_
local function _16_(root, options) local function fennelview(root, options)
local options = (options or ({})) 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}) 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) put_value(inspector, root)
return table.concat(inspector.buffer) return table.concat(inspector.buffer)
end 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 -- lume
-- --
-- Copyright (c) 2015 rxi -- Copyright (c) 2018 rxi
-- --
-- This library is free software; you can redistribute it and/or modify it -- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- under the terms of the MIT license. See LICENSE for details. -- 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 pairs, ipairs = pairs, ipairs
local type, assert, unpack = type, assert, unpack or table.unpack local type, assert, unpack = type, assert, unpack or table.unpack
local tostring, tonumber = tostring, tonumber local tostring, tonumber = tostring, tonumber
local math_floor = math.floor local math_floor = math.floor
local math_ceil = math.ceil local math_ceil = math.ceil
local math_random = math.random
local math_atan2 = math.atan2 or math.atan local math_atan2 = math.atan2 or math.atan
local math_sqrt = math.sqrt local math_sqrt = math.sqrt
local math_abs = math.abs local math_abs = math.abs
@ -111,15 +125,20 @@ function lume.angle(x1, y1, x2, y2)
end end
function lume.vector(angle, magnitude)
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
end
function lume.random(a, b) function lume.random(a, b)
if not a then a, b = 0, 1 end if not a then a, b = 0, 1 end
if not b then b = 0 end if not b then b = 0 end
return a + math_random() * (b - a) return a + math.random() * (b - a)
end end
function lume.randomchoice(t) function lume.randomchoice(t)
return t[math_random(#t)] return t[math.random(#t)]
end end
@ -139,7 +158,7 @@ end
function lume.isarray(x) 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 end
@ -194,7 +213,7 @@ end
function lume.shuffle(t) function lume.shuffle(t)
local rtn = {} local rtn = {}
for i = 1, #t do for i = 1, #t do
local r = math_random(i) local r = math.random(i)
if r ~= i then if r ~= i then
rtn[i] = rtn[r] rtn[i] = rtn[r]
end end
@ -283,7 +302,7 @@ function lume.reduce(t, fn, first)
end end
function lume.set(t) function lume.unique(t)
local rtn = {} local rtn = {}
for k in pairs(lume.invert(t)) do for k in pairs(lume.invert(t)) do
rtn[#rtn + 1] = k rtn[#rtn + 1] = k
@ -658,7 +677,7 @@ end
function lume.uuid() function lume.uuid()
local fn = function(x) 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 r = (x == "x") and (r + 1) or (r % 4) + 9
return ("0123456789abcdef"):sub(r, r) return ("0123456789abcdef"):sub(r, r)
end end
@ -705,7 +724,9 @@ end
local ripairs_iter = function(t, i) local ripairs_iter = function(t, i)
i = i - 1 i = i - 1
local v = t[i] local v = t[i]
if v then return i, v end if v ~= nil then
return i, v
end
end end
function lume.ripairs(t) function lume.ripairs(t)
@ -735,15 +756,6 @@ function lume.color(str, mul)
end 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 = {} local chain_mt = {}
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
function(fn) 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