Update for Spring 2019 Game Jam
This commit is contained in:
parent
05a2b28752
commit
ff33a7538e
|
@ -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
|
||||
|
2
conf.lua
2
conf.lua
|
@ -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
|
||||
|
|
773
lib/bump.lua
773
lib/bump.lua
|
@ -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
|
141
lib/fennel
141
lib/fennel
|
@ -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]
|
||||
|
||||
--repl : Launch an interactive repl session
|
||||
--compile FILES : Compile files and write their Lua to stdout
|
||||
--help : Display this text
|
||||
Run fennel, a lisp programming language for the Lua runtime.
|
||||
|
||||
When not given a flag, runs the file given as the first argument.]]
|
||||
--repl : Launch an interactive repl session
|
||||
--compile FILES : Compile files and write their Lua to stdout
|
||||
|
||||
--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)
|
||||
print(val)
|
||||
if not ok then os.exit(1) end
|
||||
if ok then
|
||||
print(val)
|
||||
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
|
||||
|
|
923
lib/fennel.lua
923
lib/fennel.lua
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
if not short_control_char_escapes[ch] then
|
||||
short_control_char_escapes[ch] = ("\\" .. i)
|
||||
long[ch] = ("\\%03d"):format(i)
|
||||
end
|
||||
_2_()
|
||||
end
|
||||
return long
|
||||
long_control_char_esapes = 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)
|
||||
end
|
||||
if not sequence_key_3f(k, sequence_length) then
|
||||
table.insert(keys, k)
|
||||
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
|
||||
if not appearances[t] then
|
||||
appearances[t] = 1
|
||||
for k, v in pairs(t) do
|
||||
recur(k, appearances)
|
||||
recur(v, appearances)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
else
|
||||
if (t and (t) == (t)) then
|
||||
appearances[t] = ((appearances[t] or 0) + 1)
|
||||
return nil
|
||||
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
|
||||
count_table_appearances(k, appearances)
|
||||
count_table_appearances(v, appearances)
|
||||
end
|
||||
end
|
||||
else
|
||||
if (t and (t == t)) then
|
||||
appearances[t] = ((appearances[t] or 0) + 1)
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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
|
54
lib/lume.lua
54
lib/lume.lua
|
@ -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)
|
||||
|
|
1536
lib/sfxr.lua
1536
lib/sfxr.lua
File diff suppressed because it is too large
Load diff
|
@ -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
|
1485
lib/tiled/init.lua
1485
lib/tiled/init.lua
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue