chore: remove most unused stuff from older versions
This commit is contained in:
parent
b3882b388f
commit
af7cc04da9
|
@ -28,7 +28,7 @@ local Game = Object:extend()
|
||||||
local PigManager = require "game.pigmanager"
|
local PigManager = require "game.pigmanager"
|
||||||
local Inventory = require "game.inventory"
|
local Inventory = require "game.inventory"
|
||||||
|
|
||||||
local binser = require "libs.binser"
|
local binser = require "core.libs.binser"
|
||||||
|
|
||||||
Game.gui = require "game.modules.gui"
|
Game.gui = require "game.modules.gui"
|
||||||
|
|
||||||
|
|
|
@ -1,302 +0,0 @@
|
||||||
local anim8 = {
|
|
||||||
_VERSION = 'anim8 v2.3.0',
|
|
||||||
_DESCRIPTION = 'An animation library for LÖVE',
|
|
||||||
_URL = 'https://github.com/kikito/anim8',
|
|
||||||
_LICENSE = [[
|
|
||||||
MIT LICENSE
|
|
||||||
|
|
||||||
Copyright (c) 2011 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.
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
|
|
||||||
local Grid = {}
|
|
||||||
|
|
||||||
local _frames = {}
|
|
||||||
|
|
||||||
local function assertPositiveInteger(value, name)
|
|
||||||
if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end
|
|
||||||
if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end
|
|
||||||
if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createFrame(self, x, y)
|
|
||||||
local fw, fh = self.frameWidth, self.frameHeight
|
|
||||||
return love.graphics.newQuad(
|
|
||||||
self.left + (x-1) * fw + x * self.border,
|
|
||||||
self.top + (y-1) * fh + y * self.border,
|
|
||||||
fw,
|
|
||||||
fh,
|
|
||||||
self.imageWidth,
|
|
||||||
self.imageHeight
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getGridKey(...)
|
|
||||||
return table.concat( {...} ,'-' )
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getOrCreateFrame(self, x, y)
|
|
||||||
if x < 1 or x > self.width or y < 1 or y > self.height then
|
|
||||||
error(("There is no frame for x=%d, y=%d"):format(x, y))
|
|
||||||
end
|
|
||||||
local key = self._key
|
|
||||||
_frames[key] = _frames[key] or {}
|
|
||||||
_frames[key][x] = _frames[key][x] or {}
|
|
||||||
_frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y)
|
|
||||||
return _frames[key][x][y]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parseInterval(str)
|
|
||||||
if type(str) == "number" then return str,str,1 end
|
|
||||||
str = str:gsub('%s', '') -- remove spaces
|
|
||||||
local min, max = str:match("^(%d+)-(%d+)$")
|
|
||||||
assert(min and max, ("Could not parse interval from %q"):format(str))
|
|
||||||
min, max = tonumber(min), tonumber(max)
|
|
||||||
local step = min <= max and 1 or -1
|
|
||||||
return min, max, step
|
|
||||||
end
|
|
||||||
|
|
||||||
function Grid:getFrames(...)
|
|
||||||
local result, args = {}, {...}
|
|
||||||
local minx, maxx, stepx, miny, maxy, stepy
|
|
||||||
|
|
||||||
for i=1, #args, 2 do
|
|
||||||
minx, maxx, stepx = parseInterval(args[i])
|
|
||||||
miny, maxy, stepy = parseInterval(args[i+1])
|
|
||||||
for y = miny, maxy, stepy do
|
|
||||||
for x = minx, maxx, stepx do
|
|
||||||
result[#result+1] = getOrCreateFrame(self,x,y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
local Gridmt = {
|
|
||||||
__index = Grid,
|
|
||||||
__call = Grid.getFrames
|
|
||||||
}
|
|
||||||
|
|
||||||
local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
|
|
||||||
assertPositiveInteger(frameWidth, "frameWidth")
|
|
||||||
assertPositiveInteger(frameHeight, "frameHeight")
|
|
||||||
assertPositiveInteger(imageWidth, "imageWidth")
|
|
||||||
assertPositiveInteger(imageHeight, "imageHeight")
|
|
||||||
|
|
||||||
left = left or 0
|
|
||||||
top = top or 0
|
|
||||||
border = border or 0
|
|
||||||
|
|
||||||
local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
|
|
||||||
|
|
||||||
local grid = setmetatable(
|
|
||||||
{ frameWidth = frameWidth,
|
|
||||||
frameHeight = frameHeight,
|
|
||||||
imageWidth = imageWidth,
|
|
||||||
imageHeight = imageHeight,
|
|
||||||
left = left,
|
|
||||||
top = top,
|
|
||||||
border = border,
|
|
||||||
width = math.floor(imageWidth/frameWidth),
|
|
||||||
height = math.floor(imageHeight/frameHeight),
|
|
||||||
_key = key
|
|
||||||
},
|
|
||||||
Gridmt
|
|
||||||
)
|
|
||||||
return grid
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
local Animation = {}
|
|
||||||
|
|
||||||
local function cloneArray(arr)
|
|
||||||
local result = {}
|
|
||||||
for i=1,#arr do result[i] = arr[i] end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parseDurations(durations, frameCount)
|
|
||||||
local result = {}
|
|
||||||
if type(durations) == 'number' then
|
|
||||||
for i=1,frameCount do result[i] = durations end
|
|
||||||
else
|
|
||||||
local min, max, step
|
|
||||||
for key,duration in pairs(durations) do
|
|
||||||
assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number")
|
|
||||||
min, max, step = parseInterval(key)
|
|
||||||
for i = min,max,step do result[i] = duration end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #result < frameCount then
|
|
||||||
error("The durations table has length of " .. tostring(#result) .. ", but it should be >= " .. tostring(frameCount))
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parseIntervals(durations)
|
|
||||||
local result, time = {0},0
|
|
||||||
for i=1,#durations do
|
|
||||||
time = time + durations[i]
|
|
||||||
result[i+1] = time
|
|
||||||
end
|
|
||||||
return result, time
|
|
||||||
end
|
|
||||||
|
|
||||||
local Animationmt = { __index = Animation }
|
|
||||||
local nop = function() end
|
|
||||||
|
|
||||||
local function newAnimation(frames, durations, onLoop)
|
|
||||||
local td = type(durations);
|
|
||||||
if (td ~= 'number' or durations <= 0) and td ~= 'table' then
|
|
||||||
error("durations must be a positive number. Was " .. tostring(durations) )
|
|
||||||
end
|
|
||||||
onLoop = onLoop or nop
|
|
||||||
durations = parseDurations(durations, #frames)
|
|
||||||
local intervals, totalDuration = parseIntervals(durations)
|
|
||||||
return setmetatable({
|
|
||||||
frames = cloneArray(frames),
|
|
||||||
durations = durations,
|
|
||||||
intervals = intervals,
|
|
||||||
totalDuration = totalDuration,
|
|
||||||
onLoop = onLoop,
|
|
||||||
timer = 0,
|
|
||||||
position = 1,
|
|
||||||
status = "playing",
|
|
||||||
flippedH = false,
|
|
||||||
flippedV = false
|
|
||||||
},
|
|
||||||
Animationmt
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:clone()
|
|
||||||
local newAnim = newAnimation(self.frames, self.durations, self.onLoop)
|
|
||||||
newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV
|
|
||||||
return newAnim
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:flipH()
|
|
||||||
self.flippedH = not self.flippedH
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:flipV()
|
|
||||||
self.flippedV = not self.flippedV
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
local function seekFrameIndex(intervals, timer)
|
|
||||||
local high, low, i = #intervals-1, 1, 1
|
|
||||||
|
|
||||||
while(low <= high) do
|
|
||||||
i = math.floor((low + high) / 2)
|
|
||||||
if timer > intervals[i+1] then low = i + 1
|
|
||||||
elseif timer <= intervals[i] then high = i - 1
|
|
||||||
else
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:update(dt)
|
|
||||||
if self.status ~= "playing" then return end
|
|
||||||
|
|
||||||
self.timer = self.timer + dt
|
|
||||||
local loops = math.floor(self.timer / self.totalDuration)
|
|
||||||
if loops ~= 0 then
|
|
||||||
self.timer = self.timer - self.totalDuration * loops
|
|
||||||
local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop]
|
|
||||||
f(self, loops)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.position = seekFrameIndex(self.intervals, self.timer)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:pause()
|
|
||||||
self.status = "paused"
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:gotoFrame(position)
|
|
||||||
self.position = position
|
|
||||||
self.timer = self.intervals[self.position]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:pauseAtEnd()
|
|
||||||
self.position = #self.frames
|
|
||||||
self.timer = self.totalDuration
|
|
||||||
self:pause()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:pauseAtStart()
|
|
||||||
self.position = 1
|
|
||||||
self.timer = 0
|
|
||||||
self:pause()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:resume()
|
|
||||||
self.status = "playing"
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:draw(image, x, y, r, sx, sy, ox, oy, kx, ky)
|
|
||||||
love.graphics.draw(image, self:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky))
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky)
|
|
||||||
local frame = self.frames[self.position]
|
|
||||||
if self.flippedH or self.flippedV then
|
|
||||||
r,sx,sy,ox,oy,kx,ky = r or 0, sx or 1, sy or 1, ox or 0, oy or 0, kx or 0, ky or 0
|
|
||||||
local _,_,w,h = frame:getViewport()
|
|
||||||
|
|
||||||
if self.flippedH then
|
|
||||||
sx = sx * -1
|
|
||||||
ox = w - ox
|
|
||||||
kx = kx * -1
|
|
||||||
ky = ky * -1
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.flippedV then
|
|
||||||
sy = sy * -1
|
|
||||||
oy = h - oy
|
|
||||||
kx = kx * -1
|
|
||||||
ky = ky * -1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return frame, x, y, r, sx, sy, ox, oy, kx, ky
|
|
||||||
end
|
|
||||||
|
|
||||||
function Animation:getDimensions()
|
|
||||||
local _,_,w,h = self.frames[self.position]:getViewport()
|
|
||||||
return w,h
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
anim8.newGrid = newGrid
|
|
||||||
anim8.newAnimation = newAnimation
|
|
||||||
|
|
||||||
return anim8
|
|
|
@ -1,9 +0,0 @@
|
||||||
local AssetManager = Object:extend()
|
|
||||||
|
|
||||||
local SoundManager = "libs.assets.sfx"
|
|
||||||
|
|
||||||
function AssetManager:new()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return AssetManager
|
|
|
@ -1,12 +0,0 @@
|
||||||
local SoundManager = Object:extend()
|
|
||||||
local SFX = Object:extend()
|
|
||||||
|
|
||||||
function SoundManager:new()
|
|
||||||
self.datas = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function SoundManager:addSFX()
|
|
||||||
self.datas = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
return SoundManager
|
|
|
@ -1,687 +0,0 @@
|
||||||
-- binser.lua
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Copyright (c) 2016 Calvin Rose
|
|
||||||
|
|
||||||
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 assert = assert
|
|
||||||
local error = error
|
|
||||||
local select = select
|
|
||||||
local pairs = pairs
|
|
||||||
local getmetatable = getmetatable
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local tonumber = tonumber
|
|
||||||
local type = type
|
|
||||||
local loadstring = loadstring or load
|
|
||||||
local concat = table.concat
|
|
||||||
local char = string.char
|
|
||||||
local byte = string.byte
|
|
||||||
local format = string.format
|
|
||||||
local sub = string.sub
|
|
||||||
local dump = string.dump
|
|
||||||
local floor = math.floor
|
|
||||||
local frexp = math.frexp
|
|
||||||
local unpack = unpack or table.unpack
|
|
||||||
|
|
||||||
-- Lua 5.3 frexp polyfill
|
|
||||||
-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
|
|
||||||
if not frexp then
|
|
||||||
local log, abs, floor = math.log, math.abs, math.floor
|
|
||||||
local log2 = log(2)
|
|
||||||
frexp = function(x)
|
|
||||||
if x == 0 then return 0, 0 end
|
|
||||||
local e = floor(log(abs(x)) / log2 + 1)
|
|
||||||
return x / 2 ^ e, e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- NIL = 202
|
|
||||||
-- FLOAT = 203
|
|
||||||
-- TRUE = 204
|
|
||||||
-- FALSE = 205
|
|
||||||
-- STRING = 206
|
|
||||||
-- TABLE = 207
|
|
||||||
-- REFERENCE = 208
|
|
||||||
-- CONSTRUCTOR = 209
|
|
||||||
-- FUNCTION = 210
|
|
||||||
-- RESOURCE = 211
|
|
||||||
-- INT64 = 212
|
|
||||||
|
|
||||||
local mts = {}
|
|
||||||
local ids = {}
|
|
||||||
local serializers = {}
|
|
||||||
local deserializers = {}
|
|
||||||
local resources = {}
|
|
||||||
local resources_by_name = {}
|
|
||||||
|
|
||||||
local function pack(...)
|
|
||||||
return {...}, select("#", ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function not_array_index(x, len)
|
|
||||||
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function type_check(x, tp, name)
|
|
||||||
assert(type(x) == tp,
|
|
||||||
format("Expected parameter %q to be of type %q.", name, tp))
|
|
||||||
end
|
|
||||||
|
|
||||||
local bigIntSupport = false
|
|
||||||
local isInteger
|
|
||||||
if math.type then -- Detect Lua 5.3
|
|
||||||
local mtype = math.type
|
|
||||||
bigIntSupport = loadstring[[
|
|
||||||
local char = string.char
|
|
||||||
return function(n)
|
|
||||||
local nn = n < 0 and -(n + 1) or n
|
|
||||||
local b1 = nn // 0x100000000000000
|
|
||||||
local b2 = nn // 0x1000000000000 % 0x100
|
|
||||||
local b3 = nn // 0x10000000000 % 0x100
|
|
||||||
local b4 = nn // 0x100000000 % 0x100
|
|
||||||
local b5 = nn // 0x1000000 % 0x100
|
|
||||||
local b6 = nn // 0x10000 % 0x100
|
|
||||||
local b7 = nn // 0x100 % 0x100
|
|
||||||
local b8 = nn % 0x100
|
|
||||||
if n < 0 then
|
|
||||||
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
|
|
||||||
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
|
|
||||||
end
|
|
||||||
return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
|
|
||||||
end]]()
|
|
||||||
isInteger = function(x)
|
|
||||||
return mtype(x) == 'integer'
|
|
||||||
end
|
|
||||||
else
|
|
||||||
isInteger = function(x)
|
|
||||||
return floor(x) == x
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Copyright (C) 2012-2015 Francois Perrad.
|
|
||||||
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
|
|
||||||
-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
|
|
||||||
local function number_to_str(n)
|
|
||||||
if isInteger(n) then -- int
|
|
||||||
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
|
|
||||||
return char(n + 27)
|
|
||||||
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
|
|
||||||
n = n + 8192
|
|
||||||
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
|
|
||||||
elseif bigIntSupport then
|
|
||||||
return bigIntSupport(n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sign = 0
|
|
||||||
if n < 0.0 then
|
|
||||||
sign = 0x80
|
|
||||||
n = -n
|
|
||||||
end
|
|
||||||
local m, e = frexp(n) -- mantissa, exponent
|
|
||||||
if m ~= m then
|
|
||||||
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
||||||
elseif m == 1/0 then
|
|
||||||
if sign == 0 then
|
|
||||||
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
||||||
else
|
|
||||||
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
e = e + 0x3FE
|
|
||||||
if e < 1 then -- denormalized numbers
|
|
||||||
m = m * 2 ^ (52 + e)
|
|
||||||
e = 0
|
|
||||||
else
|
|
||||||
m = (m * 2 - 1) * 2 ^ 52
|
|
||||||
end
|
|
||||||
return char(203,
|
|
||||||
sign + floor(e / 0x10),
|
|
||||||
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
|
|
||||||
floor(m / 0x10000000000) % 0x100,
|
|
||||||
floor(m / 0x100000000) % 0x100,
|
|
||||||
floor(m / 0x1000000) % 0x100,
|
|
||||||
floor(m / 0x10000) % 0x100,
|
|
||||||
floor(m / 0x100) % 0x100,
|
|
||||||
m % 0x100)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Copyright (C) 2012-2015 Francois Perrad.
|
|
||||||
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
|
|
||||||
local function number_from_str(str, index)
|
|
||||||
local b = byte(str, index)
|
|
||||||
if b < 128 then
|
|
||||||
return b - 27, index + 1
|
|
||||||
elseif b < 192 then
|
|
||||||
return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2
|
|
||||||
end
|
|
||||||
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
|
|
||||||
if b == 212 then
|
|
||||||
local flip = b1 >= 128
|
|
||||||
if flip then -- negative
|
|
||||||
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
|
|
||||||
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
|
|
||||||
end
|
|
||||||
local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
|
|
||||||
if flip then
|
|
||||||
return (-n) - 1, index + 9
|
|
||||||
else
|
|
||||||
return n, index + 9
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sign = b1 > 0x7F and -1 or 1
|
|
||||||
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
|
|
||||||
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
|
|
||||||
local n
|
|
||||||
if e == 0 then
|
|
||||||
if m == 0 then
|
|
||||||
n = sign * 0.0
|
|
||||||
else
|
|
||||||
n = sign * (m / 2 ^ 52) * 2 ^ -1022
|
|
||||||
end
|
|
||||||
elseif e == 0x7FF then
|
|
||||||
if m == 0 then
|
|
||||||
n = sign * (1/0)
|
|
||||||
else
|
|
||||||
n = 0.0/0.0
|
|
||||||
end
|
|
||||||
else
|
|
||||||
n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
|
|
||||||
end
|
|
||||||
return n, index + 9
|
|
||||||
end
|
|
||||||
|
|
||||||
local types = {}
|
|
||||||
|
|
||||||
types["nil"] = function(x, visited, accum)
|
|
||||||
accum[#accum + 1] = "\202"
|
|
||||||
end
|
|
||||||
|
|
||||||
function types.number(x, visited, accum)
|
|
||||||
accum[#accum + 1] = number_to_str(x)
|
|
||||||
end
|
|
||||||
|
|
||||||
function types.boolean(x, visited, accum)
|
|
||||||
accum[#accum + 1] = x and "\204" or "\205"
|
|
||||||
end
|
|
||||||
|
|
||||||
function types.string(x, visited, accum)
|
|
||||||
local alen = #accum
|
|
||||||
if visited[x] then
|
|
||||||
accum[alen + 1] = "\208"
|
|
||||||
accum[alen + 2] = number_to_str(visited[x])
|
|
||||||
else
|
|
||||||
visited[x] = visited.next
|
|
||||||
visited.next = visited.next + 1
|
|
||||||
accum[alen + 1] = "\206"
|
|
||||||
accum[alen + 2] = number_to_str(#x)
|
|
||||||
accum[alen + 3] = x
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_custom_type(x, visited, accum)
|
|
||||||
local res = resources[x]
|
|
||||||
if res then
|
|
||||||
accum[#accum + 1] = "\211"
|
|
||||||
types[type(res)](res, visited, accum)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
local mt = getmetatable(x)
|
|
||||||
local id = mt and ids[mt]
|
|
||||||
if id then
|
|
||||||
if x == visited.temp then
|
|
||||||
error("Infinite loop in constructor.")
|
|
||||||
end
|
|
||||||
visited.temp = x
|
|
||||||
accum[#accum + 1] = "\209"
|
|
||||||
types[type(id)](id, visited, accum)
|
|
||||||
local args, len = pack(serializers[id](x))
|
|
||||||
accum[#accum + 1] = number_to_str(len)
|
|
||||||
for i = 1, len do
|
|
||||||
local arg = args[i]
|
|
||||||
types[type(arg)](arg, visited, accum)
|
|
||||||
end
|
|
||||||
visited[x] = visited.next
|
|
||||||
visited.next = visited.next + 1
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function types.userdata(x, visited, accum)
|
|
||||||
if visited[x] then
|
|
||||||
accum[#accum + 1] = "\208"
|
|
||||||
accum[#accum + 1] = number_to_str(visited[x])
|
|
||||||
else
|
|
||||||
if check_custom_type(x, visited, accum) then return end
|
|
||||||
error("Cannot serialize this userdata.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function types.table(x, visited, accum)
|
|
||||||
if visited[x] then
|
|
||||||
accum[#accum + 1] = "\208"
|
|
||||||
accum[#accum + 1] = number_to_str(visited[x])
|
|
||||||
else
|
|
||||||
if check_custom_type(x, visited, accum) then return end
|
|
||||||
visited[x] = visited.next
|
|
||||||
visited.next = visited.next + 1
|
|
||||||
local xlen = #x
|
|
||||||
accum[#accum + 1] = "\207"
|
|
||||||
accum[#accum + 1] = number_to_str(xlen)
|
|
||||||
for i = 1, xlen do
|
|
||||||
local v = x[i]
|
|
||||||
types[type(v)](v, visited, accum)
|
|
||||||
end
|
|
||||||
local key_count = 0
|
|
||||||
for k in pairs(x) do
|
|
||||||
if not_array_index(k, xlen) then
|
|
||||||
key_count = key_count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
accum[#accum + 1] = number_to_str(key_count)
|
|
||||||
for k, v in pairs(x) do
|
|
||||||
if not_array_index(k, xlen) then
|
|
||||||
types[type(k)](k, visited, accum)
|
|
||||||
types[type(v)](v, visited, accum)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
types["function"] = function(x, visited, accum)
|
|
||||||
if visited[x] then
|
|
||||||
accum[#accum + 1] = "\208"
|
|
||||||
accum[#accum + 1] = number_to_str(visited[x])
|
|
||||||
else
|
|
||||||
if check_custom_type(x, visited, accum) then return end
|
|
||||||
visited[x] = visited.next
|
|
||||||
visited.next = visited.next + 1
|
|
||||||
local str = dump(x)
|
|
||||||
accum[#accum + 1] = "\210"
|
|
||||||
accum[#accum + 1] = number_to_str(#str)
|
|
||||||
accum[#accum + 1] = str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
types.cdata = function(x, visited, accum)
|
|
||||||
if visited[x] then
|
|
||||||
accum[#accum + 1] = "\208"
|
|
||||||
accum[#accum + 1] = number_to_str(visited[x])
|
|
||||||
else
|
|
||||||
if check_custom_type(x, visited, #accum) then return end
|
|
||||||
error("Cannot serialize this cdata.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
types.thread = function() error("Cannot serialize threads.") end
|
|
||||||
|
|
||||||
local function deserialize_value(str, index, visited)
|
|
||||||
local t = byte(str, index)
|
|
||||||
if not t then return end
|
|
||||||
if t < 128 then
|
|
||||||
return t - 27, index + 1
|
|
||||||
elseif t < 192 then
|
|
||||||
return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2
|
|
||||||
elseif t == 202 then
|
|
||||||
return nil, index + 1
|
|
||||||
elseif t == 203 then
|
|
||||||
return number_from_str(str, index)
|
|
||||||
elseif t == 204 then
|
|
||||||
return true, index + 1
|
|
||||||
elseif t == 205 then
|
|
||||||
return false, index + 1
|
|
||||||
elseif t == 206 then
|
|
||||||
local length, dataindex = deserialize_value(str, index + 1, visited)
|
|
||||||
local nextindex = dataindex + length
|
|
||||||
local substr = sub(str, dataindex, nextindex - 1)
|
|
||||||
visited[#visited + 1] = substr
|
|
||||||
return substr, nextindex
|
|
||||||
elseif t == 207 then
|
|
||||||
local count, nextindex = number_from_str(str, index + 1)
|
|
||||||
local ret = {}
|
|
||||||
visited[#visited + 1] = ret
|
|
||||||
for i = 1, count do
|
|
||||||
ret[i], nextindex = deserialize_value(str, nextindex, visited)
|
|
||||||
end
|
|
||||||
count, nextindex = number_from_str(str, nextindex)
|
|
||||||
for i = 1, count do
|
|
||||||
local k, v
|
|
||||||
k, nextindex = deserialize_value(str, nextindex, visited)
|
|
||||||
v, nextindex = deserialize_value(str, nextindex, visited)
|
|
||||||
ret[k] = v
|
|
||||||
end
|
|
||||||
return ret, nextindex
|
|
||||||
elseif t == 208 then
|
|
||||||
local ref, nextindex = number_from_str(str, index + 1)
|
|
||||||
return visited[ref], nextindex
|
|
||||||
elseif t == 209 then
|
|
||||||
local count
|
|
||||||
local name, nextindex = deserialize_value(str, index + 1, visited)
|
|
||||||
count, nextindex = number_from_str(str, nextindex)
|
|
||||||
local args = {}
|
|
||||||
for i = 1, count do
|
|
||||||
args[i], nextindex = deserialize_value(str, nextindex, visited)
|
|
||||||
end
|
|
||||||
local ret = deserializers[name](unpack(args))
|
|
||||||
visited[#visited + 1] = ret
|
|
||||||
return ret, nextindex
|
|
||||||
elseif t == 210 then
|
|
||||||
local length, dataindex = deserialize_value(str, index + 1, visited)
|
|
||||||
local nextindex = dataindex + length
|
|
||||||
local ret = loadstring(sub(str, dataindex, nextindex - 1))
|
|
||||||
visited[#visited + 1] = ret
|
|
||||||
return ret, nextindex
|
|
||||||
elseif t == 211 then
|
|
||||||
local res, nextindex = deserialize_value(str, index + 1, visited)
|
|
||||||
return resources_by_name[res], nextindex
|
|
||||||
elseif t == 212 then
|
|
||||||
return number_from_str(str, index)
|
|
||||||
else
|
|
||||||
error("Could not deserialize type byte " .. t .. ".")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function serialize(...)
|
|
||||||
local visited = {next = 1}
|
|
||||||
local accum = {}
|
|
||||||
for i = 1, select("#", ...) do
|
|
||||||
local x = select(i, ...)
|
|
||||||
types[type(x)](x, visited, accum)
|
|
||||||
end
|
|
||||||
return concat(accum)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function make_file_writer(file)
|
|
||||||
return setmetatable({}, {
|
|
||||||
__newindex = function(_, _, v)
|
|
||||||
file:write(v)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local function serialize_to_file(path, mode, ...)
|
|
||||||
local file, err = io.open(path, mode)
|
|
||||||
assert(file, err)
|
|
||||||
local visited = {next = 1}
|
|
||||||
local accum = make_file_writer(file)
|
|
||||||
for i = 1, select("#", ...) do
|
|
||||||
local x = select(i, ...)
|
|
||||||
types[type(x)](x, visited, accum)
|
|
||||||
end
|
|
||||||
-- flush the writer
|
|
||||||
file:flush()
|
|
||||||
file:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function writeFile(path, ...)
|
|
||||||
return serialize_to_file(path, "wb", ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function appendFile(path, ...)
|
|
||||||
return serialize_to_file(path, "ab", ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function deserialize(str, index)
|
|
||||||
assert(type(str) == "string", "Expected string to deserialize.")
|
|
||||||
local vals = {}
|
|
||||||
index = index or 1
|
|
||||||
local visited = {}
|
|
||||||
local len = 0
|
|
||||||
local val
|
|
||||||
while index do
|
|
||||||
val, index = deserialize_value(str, index, visited)
|
|
||||||
if index then
|
|
||||||
len = len + 1
|
|
||||||
vals[len] = val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return vals, len
|
|
||||||
end
|
|
||||||
|
|
||||||
local function deserializeN(str, n, index)
|
|
||||||
assert(type(str) == "string", "Expected string to deserialize.")
|
|
||||||
n = n or 1
|
|
||||||
assert(type(n) == "number", "Expected a number for parameter n.")
|
|
||||||
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
|
|
||||||
local vals = {}
|
|
||||||
index = index or 1
|
|
||||||
local visited = {}
|
|
||||||
local len = 0
|
|
||||||
local val
|
|
||||||
while index and len < n do
|
|
||||||
val, index = deserialize_value(str, index, visited)
|
|
||||||
if index then
|
|
||||||
len = len + 1
|
|
||||||
vals[len] = val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vals[len + 1] = index
|
|
||||||
return unpack(vals, 1, n + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function readFile(path)
|
|
||||||
local file, err = io.open(path, "rb")
|
|
||||||
assert(file, err)
|
|
||||||
local str = file:read("*all")
|
|
||||||
file:close()
|
|
||||||
return deserialize(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function default_deserialize(metatable)
|
|
||||||
return function(...)
|
|
||||||
local ret = {}
|
|
||||||
for i = 1, select("#", ...), 2 do
|
|
||||||
ret[select(i, ...)] = select(i + 1, ...)
|
|
||||||
end
|
|
||||||
return setmetatable(ret, metatable)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function default_serialize(x)
|
|
||||||
assert(type(x) == "table",
|
|
||||||
"Default serialization for custom types only works for tables.")
|
|
||||||
local args = {}
|
|
||||||
local len = 0
|
|
||||||
for k, v in pairs(x) do
|
|
||||||
args[len + 1], args[len + 2] = k, v
|
|
||||||
len = len + 2
|
|
||||||
end
|
|
||||||
return unpack(args, 1, len)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Templating
|
|
||||||
|
|
||||||
local function normalize_template(template)
|
|
||||||
local ret = {}
|
|
||||||
for i = 1, #template do
|
|
||||||
ret[i] = template[i]
|
|
||||||
end
|
|
||||||
local non_array_part = {}
|
|
||||||
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
|
|
||||||
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
|
|
||||||
-- in templates. Looking for way around this.
|
|
||||||
for k in pairs(template) do
|
|
||||||
if not_array_index(k, #template) then
|
|
||||||
non_array_part[#non_array_part + 1] = k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.sort(non_array_part)
|
|
||||||
for i = 1, #non_array_part do
|
|
||||||
local name = non_array_part[i]
|
|
||||||
ret[#ret + 1] = {name, normalize_template(template[name])}
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
local function templatepart_serialize(part, argaccum, x, len)
|
|
||||||
local extras = {}
|
|
||||||
local extracount = 0
|
|
||||||
for k, v in pairs(x) do
|
|
||||||
extras[k] = v
|
|
||||||
extracount = extracount + 1
|
|
||||||
end
|
|
||||||
for i = 1, #part do
|
|
||||||
extracount = extracount - 1
|
|
||||||
if type(part[i]) == "table" then
|
|
||||||
extras[part[i][1]] = nil
|
|
||||||
len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len)
|
|
||||||
else
|
|
||||||
extras[part[i]] = nil
|
|
||||||
len = len + 1
|
|
||||||
argaccum[len] = x[part[i]]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if extracount > 0 then
|
|
||||||
argaccum[len + 1] = extras
|
|
||||||
else
|
|
||||||
argaccum[len + 1] = nil
|
|
||||||
end
|
|
||||||
return len + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function templatepart_deserialize(ret, part, values, vindex)
|
|
||||||
for i = 1, #part do
|
|
||||||
local name = part[i]
|
|
||||||
if type(name) == "table" then
|
|
||||||
local newret = {}
|
|
||||||
ret[name[1]] = newret
|
|
||||||
vindex = templatepart_deserialize(newret, name[2], values, vindex)
|
|
||||||
else
|
|
||||||
ret[name] = values[vindex]
|
|
||||||
vindex = vindex + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local extras = values[vindex]
|
|
||||||
if extras then
|
|
||||||
for k, v in pairs(extras) do
|
|
||||||
ret[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return vindex + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function template_serializer_and_deserializer(metatable, template)
|
|
||||||
return function(x)
|
|
||||||
argaccum = {}
|
|
||||||
local len = templatepart_serialize(template, argaccum, x, 0)
|
|
||||||
return unpack(argaccum, 1, len)
|
|
||||||
end, function(...)
|
|
||||||
local ret = {}
|
|
||||||
local len = select("#", ...)
|
|
||||||
local args = {...}
|
|
||||||
templatepart_deserialize(ret, template, args, 1)
|
|
||||||
return setmetatable(ret, metatable)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function register(metatable, name, serialize, deserialize)
|
|
||||||
name = name or metatable.name
|
|
||||||
serialize = serialize or metatable._serialize
|
|
||||||
deserialize = deserialize or metatable._deserialize
|
|
||||||
if not serialize then
|
|
||||||
if metatable._template then
|
|
||||||
local t = normalize_template(metatable._template)
|
|
||||||
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
|
|
||||||
elseif not deserialize then
|
|
||||||
serialize = default_serialize
|
|
||||||
deserialize = default_deserialize(metatable)
|
|
||||||
else
|
|
||||||
serialize = metatable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
type_check(metatable, "table", "metatable")
|
|
||||||
type_check(name, "string", "name")
|
|
||||||
type_check(serialize, "function", "serialize")
|
|
||||||
type_check(deserialize, "function", "deserialize")
|
|
||||||
assert(not ids[metatable], "Metatable already registered.")
|
|
||||||
assert(not mts[name], ("Name %q already registered."):format(name))
|
|
||||||
mts[name] = metatable
|
|
||||||
ids[metatable] = name
|
|
||||||
serializers[name] = serialize
|
|
||||||
deserializers[name] = deserialize
|
|
||||||
return metatable
|
|
||||||
end
|
|
||||||
|
|
||||||
local function unregister(item)
|
|
||||||
local name, metatable
|
|
||||||
if type(item) == "string" then -- assume name
|
|
||||||
name, metatable = item, mts[item]
|
|
||||||
else -- assume metatable
|
|
||||||
name, metatable = ids[item], item
|
|
||||||
end
|
|
||||||
type_check(name, "string", "name")
|
|
||||||
type_check(metatable, "table", "metatable")
|
|
||||||
mts[name] = nil
|
|
||||||
ids[metatable] = nil
|
|
||||||
serializers[name] = nil
|
|
||||||
deserializers[name] = nil
|
|
||||||
return metatable
|
|
||||||
end
|
|
||||||
|
|
||||||
local function registerClass(class, name)
|
|
||||||
name = name or class.name
|
|
||||||
if class.__instanceDict then -- middleclass
|
|
||||||
register(class.__instanceDict, name)
|
|
||||||
else -- assume 30log or similar library
|
|
||||||
register(class, name)
|
|
||||||
end
|
|
||||||
return class
|
|
||||||
end
|
|
||||||
|
|
||||||
local function registerResource(resource, name)
|
|
||||||
type_check(name, "string", "name")
|
|
||||||
assert(not resources[resource],
|
|
||||||
"Resource already registered.")
|
|
||||||
assert(not resources_by_name[name],
|
|
||||||
format("Resource %q already exists.", name))
|
|
||||||
resources_by_name[name] = resource
|
|
||||||
resources[resource] = name
|
|
||||||
return resource
|
|
||||||
end
|
|
||||||
|
|
||||||
local function unregisterResource(name)
|
|
||||||
type_check(name, "string", "name")
|
|
||||||
assert(resources_by_name[name], format("Resource %q does not exist.", name))
|
|
||||||
local resource = resources_by_name[name]
|
|
||||||
resources_by_name[name] = nil
|
|
||||||
resources[resource] = nil
|
|
||||||
return resource
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
-- aliases
|
|
||||||
s = serialize,
|
|
||||||
d = deserialize,
|
|
||||||
dn = deserializeN,
|
|
||||||
r = readFile,
|
|
||||||
w = writeFile,
|
|
||||||
a = appendFile,
|
|
||||||
|
|
||||||
serialize = serialize,
|
|
||||||
deserialize = deserialize,
|
|
||||||
deserializeN = deserializeN,
|
|
||||||
readFile = readFile,
|
|
||||||
writeFile = writeFile,
|
|
||||||
appendFile = appendFile,
|
|
||||||
register = register,
|
|
||||||
unregister = unregister,
|
|
||||||
registerResource = registerResource,
|
|
||||||
unregisterResource = unregisterResource,
|
|
||||||
registerClass = registerClass
|
|
||||||
}
|
|
|
@ -1,775 +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
|
|
||||||
local sx, sy = tch.x, tch.y
|
|
||||||
if move.x ~= 0 or move.y ~= 0 then
|
|
||||||
if col.normal.x == 0 then
|
|
||||||
sx = goalX
|
|
||||||
else
|
|
||||||
sy = goalY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
col.slide = {x = sx, y = sy}
|
|
||||||
|
|
||||||
x,y = tch.x, tch.y
|
|
||||||
goalX, goalY = sx, sy
|
|
||||||
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
|
|
|
@ -1,99 +0,0 @@
|
||||||
--[[
|
|
||||||
CScreen v1.3 by CodeNMore
|
|
||||||
A simple way to make resolution-independent Love2D games
|
|
||||||
Tested for LOVE 0.10.1
|
|
||||||
See: https://github.com/CodeNMore/CScreen
|
|
||||||
Zlib License:
|
|
||||||
Copyright (c) 2016 CodeNMore
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in
|
|
||||||
a product, an acknowledgment in the product documentation would be appreciated
|
|
||||||
but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
--]]
|
|
||||||
|
|
||||||
local CScreen = {}
|
|
||||||
local rx, ry, ctr = 800, 600, true
|
|
||||||
local rxv, ryv, fsv, fsvr = 800, 600, 1.0, 1.0
|
|
||||||
local tx, ty, rwf, rhf = 0, 0, 800, 600
|
|
||||||
local cr, cg, cb, ca = 0, 0, 0, 255
|
|
||||||
|
|
||||||
-- Initializes CScreen with the initial size values
|
|
||||||
function CScreen.init(tw, th, cntr)
|
|
||||||
rx = tw or 800
|
|
||||||
ry = th or 600
|
|
||||||
ctr = cntr or false
|
|
||||||
CScreen.update(love.graphics.getWidth(), love.graphics.getHeight())
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Draws letterbox borders
|
|
||||||
function CScreen.cease()
|
|
||||||
if ctr then
|
|
||||||
local pr, pg, pb, pa = love.graphics.getColor()
|
|
||||||
love.graphics.setColor(cr, cg, cb, ca)
|
|
||||||
love.graphics.scale(fsvr, fsvr)
|
|
||||||
|
|
||||||
if tx ~= 0 then
|
|
||||||
love.graphics.rectangle("fill", -tx, 0, tx, rhf)
|
|
||||||
love.graphics.rectangle("fill", rxv, 0, tx, rhf)
|
|
||||||
elseif ty ~= 0 then
|
|
||||||
love.graphics.rectangle("fill", 0, -ty, rwf, ty)
|
|
||||||
love.graphics.rectangle("fill", 0, ryv, rwf, ty)
|
|
||||||
end
|
|
||||||
|
|
||||||
love.graphics.setColor(pr, pg, pb, pa)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Scales and centers all graphics properly
|
|
||||||
function CScreen.apply()
|
|
||||||
if ctr then
|
|
||||||
love.graphics.translate(tx, ty)
|
|
||||||
end
|
|
||||||
love.graphics.scale(fsv, fsv)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Updates CScreen when the window size changes
|
|
||||||
function CScreen.update(w, h)
|
|
||||||
local sx = w / rx
|
|
||||||
local sy = h / ry
|
|
||||||
fsv = math.min(sx, sy)
|
|
||||||
fsvr = 1 / fsv
|
|
||||||
-- Centering
|
|
||||||
if ctr and fsv == sx then -- Vertically
|
|
||||||
tx = 0
|
|
||||||
ty = (h / 2) - (ry * fsv / 2)
|
|
||||||
elseif ctr and fsv == sy then -- Horizontally
|
|
||||||
ty = 0
|
|
||||||
tx = (w / 2) - (rx * fsv / 2)
|
|
||||||
end
|
|
||||||
-- Variable sets
|
|
||||||
rwf = w
|
|
||||||
rhf = h
|
|
||||||
rxv = rx * fsv
|
|
||||||
ryv = ry * fsv
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Convert from window coordinates to target coordinates
|
|
||||||
function CScreen.project(x, y)
|
|
||||||
return math.floor((x - tx) / fsv), math.floor((y - ty) / fsv)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Change letterbox color
|
|
||||||
function CScreen.setColor(r, g, b, a)
|
|
||||||
cr = r
|
|
||||||
cg = g
|
|
||||||
cb = b
|
|
||||||
ca = a
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Return the table for use
|
|
||||||
return CScreen
|
|
|
@ -1,216 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2010-2015 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 _PATH = (...):match('^(.*[%./])[^%.%/]+$') or ''
|
|
||||||
local cos, sin = math.cos, math.sin
|
|
||||||
|
|
||||||
local camera = {}
|
|
||||||
camera.__index = camera
|
|
||||||
|
|
||||||
-- Movement interpolators (for camera locking/windowing)
|
|
||||||
camera.smooth = {}
|
|
||||||
|
|
||||||
function camera.smooth.none()
|
|
||||||
return function(dx,dy) return dx,dy end
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera.smooth.linear(speed)
|
|
||||||
assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed))
|
|
||||||
return function(dx,dy, s)
|
|
||||||
-- normalize direction
|
|
||||||
local d = math.sqrt(dx*dx+dy*dy)
|
|
||||||
local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal
|
|
||||||
if d > 0 then
|
|
||||||
dx,dy = dx/d, dy/d
|
|
||||||
end
|
|
||||||
|
|
||||||
return dx*dts, dy*dts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera.smooth.damped(stiffness)
|
|
||||||
assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness))
|
|
||||||
return function(dx,dy, s)
|
|
||||||
local dts = love.timer.getDelta() * (s or stiffness)
|
|
||||||
return dx*dts, dy*dts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function new(x,y, zoom, rot, smoother)
|
|
||||||
x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2
|
|
||||||
zoom = zoom or 1
|
|
||||||
rot = rot or 0
|
|
||||||
smoother = smoother or camera.smooth.none() -- for locking, see below
|
|
||||||
return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera)
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:lookAt(x,y)
|
|
||||||
self.x, self.y = x, y
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:move(dx,dy)
|
|
||||||
self.x, self.y = self.x + dx, self.y + dy
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:position()
|
|
||||||
return self.x, self.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:rotate(phi)
|
|
||||||
self.rot = self.rot + phi
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:rotateTo(phi)
|
|
||||||
self.rot = phi
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:zoom(mul)
|
|
||||||
self.scale = self.scale * mul
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:zoomTo(zoom)
|
|
||||||
self.scale = zoom
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:attach(x,y,w,h, noclip)
|
|
||||||
x,y = x or 0, y or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
|
|
||||||
self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor()
|
|
||||||
if not noclip then
|
|
||||||
love.graphics.setScissor(x,y,w,h)
|
|
||||||
end
|
|
||||||
|
|
||||||
local cx,cy = x+w/2, y+h/2
|
|
||||||
love.graphics.push()
|
|
||||||
love.graphics.translate(cx, cy)
|
|
||||||
love.graphics.scale(self.scale)
|
|
||||||
love.graphics.rotate(self.rot)
|
|
||||||
love.graphics.translate(-self.x, -self.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:detach()
|
|
||||||
love.graphics.pop()
|
|
||||||
love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh)
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:draw(...)
|
|
||||||
local x,y,w,h,noclip,func
|
|
||||||
local nargs = select("#", ...)
|
|
||||||
if nargs == 1 then
|
|
||||||
func = ...
|
|
||||||
elseif nargs == 5 then
|
|
||||||
x,y,w,h,func = ...
|
|
||||||
elseif nargs == 6 then
|
|
||||||
x,y,w,h,noclip,func = ...
|
|
||||||
else
|
|
||||||
error("Invalid arguments to camera:draw()")
|
|
||||||
end
|
|
||||||
|
|
||||||
self:attach(x,y,w,h,noclip)
|
|
||||||
func()
|
|
||||||
self:detach()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- world coordinates to camera coordinates
|
|
||||||
function camera:cameraCoords(x,y, ox,oy,w,h)
|
|
||||||
ox, oy = ox or 0, oy or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
|
|
||||||
-- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center
|
|
||||||
local c,s = cos(self.rot), sin(self.rot)
|
|
||||||
x,y = x - self.x, y - self.y
|
|
||||||
x,y = c*x - s*y, s*x + c*y
|
|
||||||
return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy
|
|
||||||
end
|
|
||||||
|
|
||||||
-- camera coordinates to world coordinates
|
|
||||||
function camera:worldCoords(x,y, ox,oy,w,h)
|
|
||||||
ox, oy = ox or 0, oy or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
|
|
||||||
-- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y)
|
|
||||||
local c,s = cos(-self.rot), sin(-self.rot)
|
|
||||||
x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale
|
|
||||||
x,y = c*x - s*y, s*x + c*y
|
|
||||||
return x+self.x, y+self.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:mousePosition(ox,oy,w,h)
|
|
||||||
local mx,my = love.mouse.getPosition()
|
|
||||||
return self:worldCoords(mx,my, ox,oy,w,h)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- camera scrolling utilities
|
|
||||||
function camera:lockX(x, smoother, ...)
|
|
||||||
local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...)
|
|
||||||
self.x = self.x + dx
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:lockY(y, smoother, ...)
|
|
||||||
local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...)
|
|
||||||
self.y = self.y + dy
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:lockPosition(x,y, smoother, ...)
|
|
||||||
return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...)
|
|
||||||
-- figure out displacement in camera coordinates
|
|
||||||
x,y = self:cameraCoords(x,y)
|
|
||||||
local dx, dy = 0,0
|
|
||||||
if x < x_min then
|
|
||||||
dx = x - x_min
|
|
||||||
elseif x > x_max then
|
|
||||||
dx = x - x_max
|
|
||||||
end
|
|
||||||
if y < y_min then
|
|
||||||
dy = y - y_min
|
|
||||||
elseif y > y_max then
|
|
||||||
dy = y - y_max
|
|
||||||
end
|
|
||||||
|
|
||||||
-- transform displacement to movement in world coordinates
|
|
||||||
local c,s = cos(-self.rot), sin(-self.rot)
|
|
||||||
dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale
|
|
||||||
|
|
||||||
-- move
|
|
||||||
self:move((smoother or self.smoother)(dx,dy,...))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- the module
|
|
||||||
return setmetatable({new = new, smooth = camera.smooth},
|
|
||||||
{__call = function(_, ...) return new(...) end})
|
|
|
@ -1,98 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2010-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 function include_helper(to, from, seen)
|
|
||||||
if from == nil then
|
|
||||||
return to
|
|
||||||
elseif type(from) ~= 'table' then
|
|
||||||
return from
|
|
||||||
elseif seen[from] then
|
|
||||||
return seen[from]
|
|
||||||
end
|
|
||||||
|
|
||||||
seen[from] = to
|
|
||||||
for k,v in pairs(from) do
|
|
||||||
k = include_helper({}, k, seen) -- keys might also be tables
|
|
||||||
if to[k] == nil then
|
|
||||||
to[k] = include_helper({}, v, seen)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return to
|
|
||||||
end
|
|
||||||
|
|
||||||
-- deeply copies `other' into `class'. keys in `other' that are already
|
|
||||||
-- defined in `class' are omitted
|
|
||||||
local function include(class, other)
|
|
||||||
return include_helper(class, other, {})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- returns a deep copy of `other'
|
|
||||||
local function clone(other)
|
|
||||||
return setmetatable(include({}, other), getmetatable(other))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function new(class)
|
|
||||||
-- mixins
|
|
||||||
class = class or {} -- class can be nil
|
|
||||||
local inc = class.__includes or {}
|
|
||||||
if getmetatable(inc) then inc = {inc} end
|
|
||||||
|
|
||||||
for _, other in ipairs(inc) do
|
|
||||||
if type(other) == "string" then
|
|
||||||
other = _G[other]
|
|
||||||
end
|
|
||||||
include(class, other)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- class implementation
|
|
||||||
class.__index = class
|
|
||||||
class.init = class.init or class[1] or function() end
|
|
||||||
class.include = class.include or include
|
|
||||||
class.clone = class.clone or clone
|
|
||||||
|
|
||||||
-- constructor call
|
|
||||||
return setmetatable(class, {__call = function(c, ...)
|
|
||||||
local o = setmetatable({}, c)
|
|
||||||
o:init(...)
|
|
||||||
return o
|
|
||||||
end})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
|
|
||||||
if class_commons ~= false and not common then
|
|
||||||
common = {}
|
|
||||||
function common.class(name, prototype, parent)
|
|
||||||
return new{__includes = {prototype, parent}}
|
|
||||||
end
|
|
||||||
function common.instance(class, ...)
|
|
||||||
return class(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- the module
|
|
||||||
return setmetatable({new = new, include = include, clone = clone},
|
|
||||||
{__call = function(_,...) return new(...) end})
|
|
|
@ -1,108 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2010-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 function __NULL__() end
|
|
||||||
|
|
||||||
-- default gamestate produces error on every callback
|
|
||||||
local state_init = setmetatable({leave = __NULL__},
|
|
||||||
{__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end})
|
|
||||||
local stack = {state_init}
|
|
||||||
local initialized_states = setmetatable({}, {__mode = "k"})
|
|
||||||
local state_is_dirty = true
|
|
||||||
|
|
||||||
local GS = {}
|
|
||||||
function GS.new(t) return t or {} end -- constructor - deprecated!
|
|
||||||
|
|
||||||
local function change_state(stack_offset, to, ...)
|
|
||||||
local pre = stack[#stack]
|
|
||||||
|
|
||||||
-- initialize only on first call
|
|
||||||
;(initialized_states[to] or to.init or __NULL__)(to)
|
|
||||||
initialized_states[to] = __NULL__
|
|
||||||
|
|
||||||
stack[#stack+stack_offset] = to
|
|
||||||
state_is_dirty = true
|
|
||||||
return (to.enter or __NULL__)(to, pre, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GS.switch(to, ...)
|
|
||||||
assert(to, "Missing argument: Gamestate to switch to")
|
|
||||||
assert(to ~= GS, "Can't call switch with colon operator")
|
|
||||||
;(stack[#stack].leave or __NULL__)(stack[#stack])
|
|
||||||
return change_state(0, to, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GS.push(to, ...)
|
|
||||||
assert(to, "Missing argument: Gamestate to switch to")
|
|
||||||
assert(to ~= GS, "Can't call push with colon operator")
|
|
||||||
return change_state(1, to, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GS.pop(...)
|
|
||||||
assert(#stack > 1, "No more states to pop!")
|
|
||||||
local pre, to = stack[#stack], stack[#stack-1]
|
|
||||||
stack[#stack] = nil
|
|
||||||
;(pre.leave or __NULL__)(pre)
|
|
||||||
state_is_dirty = true
|
|
||||||
return (to.resume or __NULL__)(to, pre, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GS.current()
|
|
||||||
return stack[#stack]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- fetch event callbacks from love.handlers
|
|
||||||
local all_callbacks = { 'draw', 'errhand', 'update' }
|
|
||||||
for k in pairs(love.handlers) do
|
|
||||||
all_callbacks[#all_callbacks+1] = k
|
|
||||||
end
|
|
||||||
|
|
||||||
function GS.registerEvents(callbacks)
|
|
||||||
local registry = {}
|
|
||||||
callbacks = callbacks or all_callbacks
|
|
||||||
for _, f in ipairs(callbacks) do
|
|
||||||
registry[f] = love[f] or __NULL__
|
|
||||||
love[f] = function(...)
|
|
||||||
registry[f](...)
|
|
||||||
return GS[f](...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- forward any undefined functions
|
|
||||||
setmetatable(GS, {__index = function(_, func)
|
|
||||||
-- call function only if at least one 'update' was called beforehand
|
|
||||||
-- (see issue #46)
|
|
||||||
if not state_is_dirty or func == 'update' then
|
|
||||||
state_is_dirty = false
|
|
||||||
return function(...)
|
|
||||||
return (stack[#stack][func] or __NULL__)(stack[#stack], ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return __NULL__
|
|
||||||
end})
|
|
||||||
|
|
||||||
return GS
|
|
|
@ -1,102 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2012-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 Registry = {}
|
|
||||||
Registry.__index = function(self, key)
|
|
||||||
return Registry[key] or (function()
|
|
||||||
local t = {}
|
|
||||||
rawset(self, key, t)
|
|
||||||
return t
|
|
||||||
end)()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:register(s, f)
|
|
||||||
self[s][f] = f
|
|
||||||
return f
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:emit(s, ...)
|
|
||||||
for f in pairs(self[s]) do
|
|
||||||
f(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:remove(s, ...)
|
|
||||||
local f = {...}
|
|
||||||
for i = 1,select('#', ...) do
|
|
||||||
self[s][f[i]] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:clear(...)
|
|
||||||
local s = {...}
|
|
||||||
for i = 1,select('#', ...) do
|
|
||||||
self[s[i]] = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:emitPattern(p, ...)
|
|
||||||
for s in pairs(self) do
|
|
||||||
if s:match(p) then self:emit(s, ...) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:registerPattern(p, f)
|
|
||||||
for s in pairs(self) do
|
|
||||||
if s:match(p) then self:register(s, f) end
|
|
||||||
end
|
|
||||||
return f
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:removePattern(p, ...)
|
|
||||||
for s in pairs(self) do
|
|
||||||
if s:match(p) then self:remove(s, ...) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Registry:clearPattern(p)
|
|
||||||
for s in pairs(self) do
|
|
||||||
if s:match(p) then self[s] = {} end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- instancing
|
|
||||||
function Registry.new()
|
|
||||||
return setmetatable({}, Registry)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- default instance
|
|
||||||
local default = Registry.new()
|
|
||||||
|
|
||||||
-- module forwards calls to default instance
|
|
||||||
local module = {}
|
|
||||||
for k in pairs(Registry) do
|
|
||||||
if k ~= "__index" then
|
|
||||||
module[k] = function(...) return default[k](default, ...) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(module, {__call = Registry.new})
|
|
|
@ -1,210 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2010-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 Timer = {}
|
|
||||||
Timer.__index = Timer
|
|
||||||
|
|
||||||
local function _nothing_() end
|
|
||||||
|
|
||||||
function Timer:update(dt)
|
|
||||||
local to_remove = {}
|
|
||||||
|
|
||||||
for handle in pairs(self.functions) do
|
|
||||||
-- handle: {
|
|
||||||
-- time = <number>,
|
|
||||||
-- after = <function>,
|
|
||||||
-- during = <function>,
|
|
||||||
-- limit = <number>,
|
|
||||||
-- count = <number>,
|
|
||||||
-- }
|
|
||||||
|
|
||||||
handle.time = handle.time + dt
|
|
||||||
handle.during(dt, math.max(handle.limit - handle.time, 0))
|
|
||||||
|
|
||||||
while handle.time >= handle.limit and handle.count > 0 do
|
|
||||||
if handle.after(handle.after) == false then
|
|
||||||
handle.count = 0
|
|
||||||
break
|
|
||||||
end
|
|
||||||
handle.time = handle.time - handle.limit
|
|
||||||
handle.count = handle.count - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if handle.count == 0 then
|
|
||||||
table.insert(to_remove, handle)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, #to_remove do
|
|
||||||
self.functions[to_remove[i]] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:during(delay, during, after)
|
|
||||||
local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 }
|
|
||||||
self.functions[handle] = true
|
|
||||||
return handle
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:after(delay, func)
|
|
||||||
return self:during(delay, _nothing_, func)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:every(delay, after, count)
|
|
||||||
local count = count or math.huge -- exploit below: math.huge - 1 = math.huge
|
|
||||||
local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count }
|
|
||||||
self.functions[handle] = true
|
|
||||||
return handle
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:cancel(handle)
|
|
||||||
self.functions[handle] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:clear()
|
|
||||||
self.functions = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function Timer:script(f)
|
|
||||||
local co = coroutine.wrap(f)
|
|
||||||
co(function(t)
|
|
||||||
self:after(t, co)
|
|
||||||
coroutine.yield()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
Timer.tween = setmetatable({
|
|
||||||
-- helper functions
|
|
||||||
out = function(f) -- 'rotates' a function
|
|
||||||
return function(s, ...) return 1 - f(1-s, ...) end
|
|
||||||
end,
|
|
||||||
chain = function(f1, f2) -- concatenates two functions
|
|
||||||
return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- useful tweening functions
|
|
||||||
linear = function(s) return s end,
|
|
||||||
quad = function(s) return s*s end,
|
|
||||||
cubic = function(s) return s*s*s end,
|
|
||||||
quart = function(s) return s*s*s*s end,
|
|
||||||
quint = function(s) return s*s*s*s*s end,
|
|
||||||
sine = function(s) return 1-math.cos(s*math.pi/2) end,
|
|
||||||
expo = function(s) return 2^(10*(s-1)) end,
|
|
||||||
circ = function(s) return 1 - math.sqrt(1-s*s) end,
|
|
||||||
|
|
||||||
back = function(s,bounciness)
|
|
||||||
bounciness = bounciness or 1.70158
|
|
||||||
return s*s*((bounciness+1)*s - bounciness)
|
|
||||||
end,
|
|
||||||
|
|
||||||
bounce = function(s) -- magic numbers ahead
|
|
||||||
local a,b = 7.5625, 1/2.75
|
|
||||||
return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375)
|
|
||||||
end,
|
|
||||||
|
|
||||||
elastic = function(s, amp, period)
|
|
||||||
amp, period = amp and math.max(1, amp) or 1, period or .3
|
|
||||||
return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1))
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
|
|
||||||
-- register new tween
|
|
||||||
__call = function(tween, self, len, subject, target, method, after, ...)
|
|
||||||
-- recursively collects fields that are defined in both subject and target into a flat list
|
|
||||||
local function tween_collect_payload(subject, target, out)
|
|
||||||
for k,v in pairs(target) do
|
|
||||||
local ref = subject[k]
|
|
||||||
assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".')
|
|
||||||
if type(v) == 'table' then
|
|
||||||
tween_collect_payload(ref, v, out)
|
|
||||||
else
|
|
||||||
local ok, delta = pcall(function() return (v-ref)*1 end)
|
|
||||||
assert(ok, 'Field "'..k..'" does not support arithmetic operations')
|
|
||||||
out[#out+1] = {subject, k, delta}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
method = tween[method or 'linear'] -- see __index
|
|
||||||
local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...}
|
|
||||||
|
|
||||||
local last_s = 0
|
|
||||||
return self:during(len, function(dt)
|
|
||||||
t = t + dt
|
|
||||||
local s = method(math.min(1, t/len), unpack(args))
|
|
||||||
local ds = s - last_s
|
|
||||||
last_s = s
|
|
||||||
for _, info in ipairs(payload) do
|
|
||||||
local ref, key, delta = unpack(info)
|
|
||||||
ref[key] = ref[key] + delta * ds
|
|
||||||
end
|
|
||||||
end, after)
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- fetches function and generated compositions for method `key`
|
|
||||||
__index = function(tweens, key)
|
|
||||||
if type(key) == 'function' then return key end
|
|
||||||
|
|
||||||
assert(type(key) == 'string', 'Method must be function or string.')
|
|
||||||
if rawget(tweens, key) then return rawget(tweens, key) end
|
|
||||||
|
|
||||||
local function construct(pattern, f)
|
|
||||||
local method = rawget(tweens, key:match(pattern))
|
|
||||||
if method then return f(method) end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local out, chain = rawget(tweens,'out'), rawget(tweens,'chain')
|
|
||||||
return construct('^in%-([^-]+)$', function(...) return ... end)
|
|
||||||
or construct('^out%-([^-]+)$', out)
|
|
||||||
or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end)
|
|
||||||
or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end)
|
|
||||||
or error('Unknown interpolation method: ' .. key)
|
|
||||||
end})
|
|
||||||
|
|
||||||
-- Timer instancing
|
|
||||||
function Timer.new()
|
|
||||||
return setmetatable({functions = {}, tween = Timer.tween}, Timer)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- default instance
|
|
||||||
local default = Timer.new()
|
|
||||||
|
|
||||||
-- module forwards calls to default instance
|
|
||||||
local module = {}
|
|
||||||
for k in pairs(Timer) do
|
|
||||||
if k ~= "__index" then
|
|
||||||
module[k] = function(...) return default[k](default, ...) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
module.tween = setmetatable({}, {
|
|
||||||
__index = Timer.tween,
|
|
||||||
__newindex = function(k,v) Timer.tween[k] = v end,
|
|
||||||
__call = function(t, ...) return default:tween(...) end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return setmetatable(module, {__call = Timer.new})
|
|
|
@ -1,172 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2012-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2
|
|
||||||
|
|
||||||
local function str(x,y)
|
|
||||||
return "("..tonumber(x)..","..tonumber(y)..")"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function mul(s, x,y)
|
|
||||||
return s*x, s*y
|
|
||||||
end
|
|
||||||
|
|
||||||
local function div(s, x,y)
|
|
||||||
return x/s, y/s
|
|
||||||
end
|
|
||||||
|
|
||||||
local function add(x1,y1, x2,y2)
|
|
||||||
return x1+x2, y1+y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sub(x1,y1, x2,y2)
|
|
||||||
return x1-x2, y1-y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function permul(x1,y1, x2,y2)
|
|
||||||
return x1*x2, y1*y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dot(x1,y1, x2,y2)
|
|
||||||
return x1*x2 + y1*y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function det(x1,y1, x2,y2)
|
|
||||||
return x1*y2 - y1*x2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function eq(x1,y1, x2,y2)
|
|
||||||
return x1 == x2 and y1 == y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function lt(x1,y1, x2,y2)
|
|
||||||
return x1 < x2 or (x1 == x2 and y1 < y2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function le(x1,y1, x2,y2)
|
|
||||||
return x1 <= x2 and y1 <= y2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function len2(x,y)
|
|
||||||
return x*x + y*y
|
|
||||||
end
|
|
||||||
|
|
||||||
local function len(x,y)
|
|
||||||
return sqrt(x*x + y*y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fromPolar(angle, radius)
|
|
||||||
return cos(angle)*radius, sin(angle)*radius
|
|
||||||
end
|
|
||||||
|
|
||||||
local function toPolar(x, y)
|
|
||||||
return atan2(y,x), len(x,y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dist2(x1,y1, x2,y2)
|
|
||||||
return len2(x1-x2, y1-y2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dist(x1,y1, x2,y2)
|
|
||||||
return len(x1-x2, y1-y2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function normalize(x,y)
|
|
||||||
local l = len(x,y)
|
|
||||||
if l > 0 then
|
|
||||||
return x/l, y/l
|
|
||||||
end
|
|
||||||
return x,y
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rotate(phi, x,y)
|
|
||||||
local c, s = cos(phi), sin(phi)
|
|
||||||
return c*x - s*y, s*x + c*y
|
|
||||||
end
|
|
||||||
|
|
||||||
local function perpendicular(x,y)
|
|
||||||
return -y, x
|
|
||||||
end
|
|
||||||
|
|
||||||
local function project(x,y, u,v)
|
|
||||||
local s = (x*u + y*v) / (u*u + v*v)
|
|
||||||
return s*u, s*v
|
|
||||||
end
|
|
||||||
|
|
||||||
local function mirror(x,y, u,v)
|
|
||||||
local s = 2 * (x*u + y*v) / (u*u + v*v)
|
|
||||||
return s*u - x, s*v - y
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
|
||||||
local function trim(maxLen, x, y)
|
|
||||||
local s = maxLen * maxLen / len2(x, y)
|
|
||||||
s = s > 1 and 1 or math.sqrt(s)
|
|
||||||
return x * s, y * s
|
|
||||||
end
|
|
||||||
|
|
||||||
local function angleTo(x,y, u,v)
|
|
||||||
if u and v then
|
|
||||||
return atan2(y, x) - atan2(v, u)
|
|
||||||
end
|
|
||||||
return atan2(y, x)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- the module
|
|
||||||
return {
|
|
||||||
str = str,
|
|
||||||
|
|
||||||
fromPolar = fromPolar,
|
|
||||||
toPolar = toPolar,
|
|
||||||
|
|
||||||
-- arithmetic
|
|
||||||
mul = mul,
|
|
||||||
div = div,
|
|
||||||
add = add,
|
|
||||||
sub = sub,
|
|
||||||
permul = permul,
|
|
||||||
dot = dot,
|
|
||||||
det = det,
|
|
||||||
cross = det,
|
|
||||||
|
|
||||||
-- relation
|
|
||||||
eq = eq,
|
|
||||||
lt = lt,
|
|
||||||
le = le,
|
|
||||||
|
|
||||||
-- misc operations
|
|
||||||
len2 = len2,
|
|
||||||
len = len,
|
|
||||||
dist2 = dist2,
|
|
||||||
dist = dist,
|
|
||||||
normalize = normalize,
|
|
||||||
rotate = rotate,
|
|
||||||
perpendicular = perpendicular,
|
|
||||||
project = project,
|
|
||||||
mirror = mirror,
|
|
||||||
trim = trim,
|
|
||||||
angleTo = angleTo,
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
--[[
|
|
||||||
Copyright (c) 2010-2013 Matthias Richter
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name(s) of the above copyright holders
|
|
||||||
shall not be used in advertising or otherwise to promote the sale, use or
|
|
||||||
other dealings in this Software without prior written authorization.
|
|
||||||
|
|
||||||
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 assert = assert
|
|
||||||
local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2
|
|
||||||
|
|
||||||
local vector = {}
|
|
||||||
vector.__index = vector
|
|
||||||
|
|
||||||
local function new(x,y)
|
|
||||||
return setmetatable({x = x or 0, y = y or 0}, vector)
|
|
||||||
end
|
|
||||||
local zero = new(0,0)
|
|
||||||
|
|
||||||
local function fromPolar(angle, radius)
|
|
||||||
return new(cos(angle) * radius, sin(angle) * radius)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isvector(v)
|
|
||||||
return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number'
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:clone()
|
|
||||||
return new(self.x, self.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:unpack()
|
|
||||||
return self.x, self.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:__tostring()
|
|
||||||
return "("..tonumber(self.x)..","..tonumber(self.y)..")"
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__unm(a)
|
|
||||||
return new(-a.x, -a.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__add(a,b)
|
|
||||||
assert(isvector(a) and isvector(b), "Add: wrong argument types (<vector> expected)")
|
|
||||||
return new(a.x+b.x, a.y+b.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__sub(a,b)
|
|
||||||
assert(isvector(a) and isvector(b), "Sub: wrong argument types (<vector> expected)")
|
|
||||||
return new(a.x-b.x, a.y-b.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__mul(a,b)
|
|
||||||
if type(a) == "number" then
|
|
||||||
return new(a*b.x, a*b.y)
|
|
||||||
elseif type(b) == "number" then
|
|
||||||
return new(b*a.x, b*a.y)
|
|
||||||
else
|
|
||||||
assert(isvector(a) and isvector(b), "Mul: wrong argument types (<vector> or <number> expected)")
|
|
||||||
return a.x*b.x + a.y*b.y
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__div(a,b)
|
|
||||||
assert(isvector(a) and type(b) == "number", "wrong argument types (expected <vector> / <number>)")
|
|
||||||
return new(a.x / b, a.y / b)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__eq(a,b)
|
|
||||||
return a.x == b.x and a.y == b.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__lt(a,b)
|
|
||||||
return a.x < b.x or (a.x == b.x and a.y < b.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.__le(a,b)
|
|
||||||
return a.x <= b.x and a.y <= b.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.permul(a,b)
|
|
||||||
assert(isvector(a) and isvector(b), "permul: wrong argument types (<vector> expected)")
|
|
||||||
return new(a.x*b.x, a.y*b.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:toPolar()
|
|
||||||
return new(atan2(self.x, self.y), self:len())
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:len2()
|
|
||||||
return self.x * self.x + self.y * self.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:len()
|
|
||||||
return sqrt(self.x * self.x + self.y * self.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.dist(a, b)
|
|
||||||
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
|
|
||||||
local dx = a.x - b.x
|
|
||||||
local dy = a.y - b.y
|
|
||||||
return sqrt(dx * dx + dy * dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector.dist2(a, b)
|
|
||||||
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
|
|
||||||
local dx = a.x - b.x
|
|
||||||
local dy = a.y - b.y
|
|
||||||
return (dx * dx + dy * dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:normalizeInplace()
|
|
||||||
local l = self:len()
|
|
||||||
if l > 0 then
|
|
||||||
self.x, self.y = self.x / l, self.y / l
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:normalized()
|
|
||||||
return self:clone():normalizeInplace()
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:rotateInplace(phi)
|
|
||||||
local c, s = cos(phi), sin(phi)
|
|
||||||
self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:rotated(phi)
|
|
||||||
local c, s = cos(phi), sin(phi)
|
|
||||||
return new(c * self.x - s * self.y, s * self.x + c * self.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:perpendicular()
|
|
||||||
return new(-self.y, self.x)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:projectOn(v)
|
|
||||||
assert(isvector(v), "invalid argument: cannot project vector on " .. type(v))
|
|
||||||
-- (self * v) * v / v:len2()
|
|
||||||
local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
|
|
||||||
return new(s * v.x, s * v.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:mirrorOn(v)
|
|
||||||
assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v))
|
|
||||||
-- 2 * self:projectOn(v) - self
|
|
||||||
local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
|
|
||||||
return new(s * v.x - self.x, s * v.y - self.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:cross(v)
|
|
||||||
assert(isvector(v), "cross: wrong argument types (<vector> expected)")
|
|
||||||
return self.x * v.y - self.y * v.x
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
|
||||||
function vector:trimInplace(maxLen)
|
|
||||||
local s = maxLen * maxLen / self:len2()
|
|
||||||
s = (s > 1 and 1) or math.sqrt(s)
|
|
||||||
self.x, self.y = self.x * s, self.y * s
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:angleTo(other)
|
|
||||||
if other then
|
|
||||||
return atan2(self.y, self.x) - atan2(other.y, other.x)
|
|
||||||
end
|
|
||||||
return atan2(self.y, self.x)
|
|
||||||
end
|
|
||||||
|
|
||||||
function vector:trimmed(maxLen)
|
|
||||||
return self:clone():trimInplace(maxLen)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- the module
|
|
||||||
return setmetatable({new = new, fromPolar = fromPolar, isvector = isvector, zero = zero},
|
|
||||||
{__call = function(_, ...) return new(...) end})
|
|
|
@ -1,737 +0,0 @@
|
||||||
--
|
|
||||||
-- lovebird
|
|
||||||
--
|
|
||||||
-- Copyright (c) 2017 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.
|
|
||||||
--
|
|
||||||
|
|
||||||
local socket = require "socket"
|
|
||||||
|
|
||||||
local lovebird = { _version = "0.4.3" }
|
|
||||||
|
|
||||||
lovebird.loadstring = loadstring or load
|
|
||||||
lovebird.inited = false
|
|
||||||
lovebird.host = "*"
|
|
||||||
lovebird.buffer = ""
|
|
||||||
lovebird.lines = {}
|
|
||||||
lovebird.connections = {}
|
|
||||||
lovebird.pages = {}
|
|
||||||
|
|
||||||
lovebird.wrapprint = true
|
|
||||||
lovebird.timestamp = true
|
|
||||||
lovebird.allowhtml = false
|
|
||||||
lovebird.echoinput = true
|
|
||||||
lovebird.port = 8000
|
|
||||||
lovebird.whitelist = { "127.0.0.1" }
|
|
||||||
lovebird.maxlines = 200
|
|
||||||
lovebird.updateinterval = .5
|
|
||||||
|
|
||||||
|
|
||||||
lovebird.pages["index"] = [[
|
|
||||||
<?lua
|
|
||||||
-- Handle console input
|
|
||||||
if req.parsedbody.input then
|
|
||||||
local str = req.parsedbody.input
|
|
||||||
if lovebird.echoinput then
|
|
||||||
lovebird.pushline({ type = 'input', str = str })
|
|
||||||
end
|
|
||||||
if str:find("^=") then
|
|
||||||
str = "print(" .. str:sub(2) .. ")"
|
|
||||||
end
|
|
||||||
xpcall(function() assert(lovebird.loadstring(str, "input"))() end,
|
|
||||||
lovebird.onerror)
|
|
||||||
end
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>lovebird</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: helvetica, verdana, sans;
|
|
||||||
background: #FFFFFF;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
.timestamp {
|
|
||||||
color: #909090;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
.repeatcount {
|
|
||||||
color: #F0F0F0;
|
|
||||||
background: #505050;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-right: 4px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
border-radius: 7px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.errormarker {
|
|
||||||
color: #F0F0F0;
|
|
||||||
background: #8E0000;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 17px;
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.greybordered {
|
|
||||||
margin: 12px;
|
|
||||||
background: #F0F0F0;
|
|
||||||
border: 1px solid #E0E0E0;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.inputline {
|
|
||||||
font-family: mono, courier;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606060;
|
|
||||||
}
|
|
||||||
.inputline:before {
|
|
||||||
content: '\00B7\00B7\00B7';
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
.errorline {
|
|
||||||
color: #8E0000;
|
|
||||||
}
|
|
||||||
#header {
|
|
||||||
background: #101010;
|
|
||||||
height: 25px;
|
|
||||||
color: #F0F0F0;
|
|
||||||
padding: 9px
|
|
||||||
}
|
|
||||||
#title {
|
|
||||||
float: left;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
#title a {
|
|
||||||
color: #F0F0F0;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#title a:hover {
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
#version {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
#status {
|
|
||||||
float: right;
|
|
||||||
font-size: 14px;
|
|
||||||
padding-top: 4px;
|
|
||||||
}
|
|
||||||
#main a {
|
|
||||||
color: #000000;
|
|
||||||
text-decoration: none;
|
|
||||||
background: #E0E0E0;
|
|
||||||
border: 1px solid #D0D0D0;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding-left: 2px;
|
|
||||||
padding-right: 2px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
#main a:hover {
|
|
||||||
background: #D0D0D0;
|
|
||||||
border: 1px solid #C0C0C0;
|
|
||||||
}
|
|
||||||
#console {
|
|
||||||
position: absolute;
|
|
||||||
top: 40px; bottom: 0px; left: 0px; right: 312px;
|
|
||||||
}
|
|
||||||
#input {
|
|
||||||
position: absolute;
|
|
||||||
margin: 10px;
|
|
||||||
bottom: 0px; left: 0px; right: 0px;
|
|
||||||
}
|
|
||||||
#inputbox {
|
|
||||||
width: 100%;
|
|
||||||
font-family: mono, courier;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
#output {
|
|
||||||
overflow-y: scroll;
|
|
||||||
position: absolute;
|
|
||||||
margin: 10px;
|
|
||||||
line-height: 17px;
|
|
||||||
top: 0px; bottom: 36px; left: 0px; right: 0px;
|
|
||||||
}
|
|
||||||
#env {
|
|
||||||
position: absolute;
|
|
||||||
top: 40px; bottom: 0px; right: 0px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
#envheader {
|
|
||||||
padding: 5px;
|
|
||||||
background: #E0E0E0;
|
|
||||||
}
|
|
||||||
#envvars {
|
|
||||||
position: absolute;
|
|
||||||
left: 0px; right: 0px; top: 25px; bottom: 0px;
|
|
||||||
margin: 10px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="header">
|
|
||||||
<div id="title">
|
|
||||||
<a href="https://github.com/rxi/lovebird">lovebird</a>
|
|
||||||
<span id="version"><?lua echo(lovebird._version) ?></span>
|
|
||||||
</div>
|
|
||||||
<div id="status"></div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div id="console" class="greybordered">
|
|
||||||
<div id="output"> <?lua echo(lovebird.buffer) ?> </div>
|
|
||||||
<div id="input">
|
|
||||||
<form method="post"
|
|
||||||
onkeydown="return onInputKeyDown(event);"
|
|
||||||
onsubmit="onInputSubmit(); return false;">
|
|
||||||
<input id="inputbox" name="input" type="text"
|
|
||||||
autocomplete="off"></input>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="env" class="greybordered">
|
|
||||||
<div id="envheader"></div>
|
|
||||||
<div id="envvars"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
document.getElementById("inputbox").focus();
|
|
||||||
|
|
||||||
var changeFavicon = function(href) {
|
|
||||||
var old = document.getElementById("favicon");
|
|
||||||
if (old) document.head.removeChild(old);
|
|
||||||
var link = document.createElement("link");
|
|
||||||
link.id = "favicon";
|
|
||||||
link.rel = "shortcut icon";
|
|
||||||
link.href = href;
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
var truncate = function(str, len) {
|
|
||||||
if (str.length <= len) return str;
|
|
||||||
return str.substring(0, len - 3) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
var geturl = function(url, onComplete, onFail) {
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
req.onreadystatechange = function() {
|
|
||||||
if (req.readyState != 4) return;
|
|
||||||
if (req.status == 200) {
|
|
||||||
if (onComplete) onComplete(req.responseText);
|
|
||||||
} else {
|
|
||||||
if (onFail) onFail(req.responseText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url += (url.indexOf("?") > -1 ? "&_=" : "?_=") + Math.random();
|
|
||||||
req.open("GET", url, true);
|
|
||||||
req.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
var divContentCache = {}
|
|
||||||
var updateDivContent = function(id, content) {
|
|
||||||
if (divContentCache[id] != content) {
|
|
||||||
document.getElementById(id).innerHTML = content;
|
|
||||||
divContentCache[id] = content
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var onInputSubmit = function() {
|
|
||||||
var b = document.getElementById("inputbox");
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
req.open("POST", "/", true);
|
|
||||||
req.send("input=" + encodeURIComponent(b.value));
|
|
||||||
/* Do input history */
|
|
||||||
if (b.value && inputHistory[0] != b.value) {
|
|
||||||
inputHistory.unshift(b.value);
|
|
||||||
}
|
|
||||||
inputHistory.index = -1;
|
|
||||||
/* Reset */
|
|
||||||
b.value = "";
|
|
||||||
refreshOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Input box history */
|
|
||||||
var inputHistory = [];
|
|
||||||
inputHistory.index = 0;
|
|
||||||
var onInputKeyDown = function(e) {
|
|
||||||
var key = e.which || e.keyCode;
|
|
||||||
if (key != 38 && key != 40) return true;
|
|
||||||
var b = document.getElementById("inputbox");
|
|
||||||
if (key == 38 && inputHistory.index < inputHistory.length - 1) {
|
|
||||||
/* Up key */
|
|
||||||
inputHistory.index++;
|
|
||||||
}
|
|
||||||
if (key == 40 && inputHistory.index >= 0) {
|
|
||||||
/* Down key */
|
|
||||||
inputHistory.index--;
|
|
||||||
}
|
|
||||||
b.value = inputHistory[inputHistory.index] || "";
|
|
||||||
b.selectionStart = b.value.length;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Output buffer and status */
|
|
||||||
var refreshOutput = function() {
|
|
||||||
geturl("/buffer", function(text) {
|
|
||||||
updateDivContent("status", "connected ●");
|
|
||||||
if (updateDivContent("output", text)) {
|
|
||||||
var div = document.getElementById("output");
|
|
||||||
div.scrollTop = div.scrollHeight;
|
|
||||||
}
|
|
||||||
/* Update favicon */
|
|
||||||
changeFavicon("data:image/png;base64," +
|
|
||||||
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAAAAAAAAAD////19fUO"+
|
|
||||||
"Dg7v7+/h4eGzs7MlJSUeHh7n5+fY2NjJycnGxsa3t7eioqKfn5+QkJCHh4d+fn7zU+b5AAAAAnRS"+
|
|
||||||
"TlPlAFWaypEAAABRSURBVBjTfc9HDoAwDERRQ+w0ern/WQkZaUBC4e/mrWzppH9VJjbjZg1Ii2rM"+
|
|
||||||
"DyR1JZ8J0dVWggIGggcEwgbYCRbuPRqgyjHNpzUP+39GPu9fgloC5L9DO0sAAAAASUVORK5CYII="
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function(text) {
|
|
||||||
updateDivContent("status", "disconnected ○");
|
|
||||||
/* Update favicon */
|
|
||||||
changeFavicon("data:image/png;base64," +
|
|
||||||
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAAAAAAAADZ2dm4uLgM"+
|
|
||||||
"DAz29vbz8/Pv7+/h4eHIyMiwsLBtbW0lJSUeHh4QEBDn5+fS0tLDw8O0tLSioqKfn5+QkJCHh4d+"+
|
|
||||||
"fn5ycnJmZmZgYGBXV1dLS0tFRUUGBgZ0He44AAAAAnRSTlPlAFWaypEAAABeSURBVBjTfY9HDoAw"+
|
|
||||||
"DAQD6Z3ey/9/iXMxkVDYw0g7F3tJReosUKHnwY4pCM+EtOEVXrb7wVRA0dMbaAcUwiVeDQq1Jp4a"+
|
|
||||||
"xUg5kE0ooqZu68Di2Tgbs/DiY/9jyGf+AyFKBAK7KD2TAAAAAElFTkSuQmCC"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setInterval(refreshOutput,
|
|
||||||
<?lua echo(lovebird.updateinterval) ?> * 1000);
|
|
||||||
|
|
||||||
/* Environment variable view */
|
|
||||||
var envPath = "";
|
|
||||||
var refreshEnv = function() {
|
|
||||||
geturl("/env.json?p=" + envPath, function(text) {
|
|
||||||
var json = eval("(" + text + ")");
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
var html = "<a href='#' onclick=\"setEnvPath('')\">env</a>";
|
|
||||||
var acc = "";
|
|
||||||
var p = json.path != "" ? json.path.split(".") : [];
|
|
||||||
for (var i = 0; i < p.length; i++) {
|
|
||||||
acc += "." + p[i];
|
|
||||||
html += " <a href='#' onclick=\"setEnvPath('" + acc + "')\">" +
|
|
||||||
truncate(p[i], 10) + "</a>";
|
|
||||||
}
|
|
||||||
updateDivContent("envheader", html);
|
|
||||||
|
|
||||||
/* Handle invalid table path */
|
|
||||||
if (!json.valid) {
|
|
||||||
updateDivContent("envvars", "Bad path");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Variables */
|
|
||||||
var html = "<table>";
|
|
||||||
for (var i = 0; json.vars[i]; i++) {
|
|
||||||
var x = json.vars[i];
|
|
||||||
var fullpath = (json.path + "." + x.key).replace(/^\./, "");
|
|
||||||
var k = truncate(x.key, 15);
|
|
||||||
if (x.type == "table") {
|
|
||||||
k = "<a href='#' onclick=\"setEnvPath('" + fullpath + "')\">" +
|
|
||||||
k + "</a>";
|
|
||||||
}
|
|
||||||
var v = "<a href='#' onclick=\"insertVar('" +
|
|
||||||
fullpath.replace(/\.(-?[0-9]+)/g, "[$1]") +
|
|
||||||
"');\">" + x.value + "</a>"
|
|
||||||
html += "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
|
|
||||||
}
|
|
||||||
html += "</table>";
|
|
||||||
updateDivContent("envvars", html);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var setEnvPath = function(p) {
|
|
||||||
envPath = p;
|
|
||||||
refreshEnv();
|
|
||||||
}
|
|
||||||
var insertVar = function(p) {
|
|
||||||
var b = document.getElementById("inputbox");
|
|
||||||
b.value += p;
|
|
||||||
b.focus();
|
|
||||||
}
|
|
||||||
setInterval(refreshEnv, <?lua echo(lovebird.updateinterval) ?> * 1000);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
]]
|
|
||||||
|
|
||||||
|
|
||||||
lovebird.pages["buffer"] = [[ <?lua echo(lovebird.buffer) ?> ]]
|
|
||||||
|
|
||||||
|
|
||||||
lovebird.pages["env.json"] = [[
|
|
||||||
<?lua
|
|
||||||
local t = _G
|
|
||||||
local p = req.parsedurl.query.p or ""
|
|
||||||
p = p:gsub("%.+", "."):match("^[%.]*(.*)[%.]*$")
|
|
||||||
if p ~= "" then
|
|
||||||
for x in p:gmatch("[^%.]+") do
|
|
||||||
t = t[x] or t[tonumber(x)]
|
|
||||||
-- Return early if path does not exist
|
|
||||||
if type(t) ~= "table" then
|
|
||||||
echo('{ "valid": false, "path": ' .. string.format("%q", p) .. ' }')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
?>
|
|
||||||
{
|
|
||||||
"valid": true,
|
|
||||||
"path": "<?lua echo(p) ?>",
|
|
||||||
"vars": [
|
|
||||||
<?lua
|
|
||||||
local keys = {}
|
|
||||||
for k in pairs(t) do
|
|
||||||
if type(k) == "number" or type(k) == "string" then
|
|
||||||
table.insert(keys, k)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.sort(keys, lovebird.compare)
|
|
||||||
for _, k in pairs(keys) do
|
|
||||||
local v = t[k]
|
|
||||||
?>
|
|
||||||
{
|
|
||||||
"key": "<?lua echo(k) ?>",
|
|
||||||
"value": <?lua echo(
|
|
||||||
string.format("%q",
|
|
||||||
lovebird.truncate(
|
|
||||||
lovebird.htmlescape(
|
|
||||||
tostring(v)), 26))) ?>,
|
|
||||||
"type": "<?lua echo(type(v)) ?>",
|
|
||||||
},
|
|
||||||
<?lua end ?>
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.init()
|
|
||||||
-- Init server
|
|
||||||
lovebird.server = assert(socket.bind(lovebird.host, lovebird.port))
|
|
||||||
lovebird.addr, lovebird.port = lovebird.server:getsockname()
|
|
||||||
lovebird.server:settimeout(0)
|
|
||||||
-- Wrap print
|
|
||||||
lovebird.origprint = print
|
|
||||||
if lovebird.wrapprint then
|
|
||||||
local oldprint = print
|
|
||||||
print = function(...)
|
|
||||||
oldprint(...)
|
|
||||||
lovebird.print(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Compile page templates
|
|
||||||
for k, page in pairs(lovebird.pages) do
|
|
||||||
lovebird.pages[k] = lovebird.template(page, "lovebird, req",
|
|
||||||
"pages." .. k)
|
|
||||||
end
|
|
||||||
lovebird.inited = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.template(str, params, chunkname)
|
|
||||||
params = params and ("," .. params) or ""
|
|
||||||
local f = function(x) return string.format(" echo(%q)", x) end
|
|
||||||
str = ("?>"..str.."<?lua"):gsub("%?>(.-)<%?lua", f)
|
|
||||||
str = "local echo " .. params .. " = ..." .. str
|
|
||||||
local fn = assert(lovebird.loadstring(str, chunkname))
|
|
||||||
return function(...)
|
|
||||||
local output = {}
|
|
||||||
local echo = function(str) table.insert(output, str) end
|
|
||||||
fn(echo, ...)
|
|
||||||
return table.concat(lovebird.map(output, tostring))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.map(t, fn)
|
|
||||||
local res = {}
|
|
||||||
for k, v in pairs(t) do res[k] = fn(v) end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.trace(...)
|
|
||||||
local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
|
|
||||||
print(str)
|
|
||||||
if not lovebird.wrapprint then lovebird.print(str) end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.unescape(str)
|
|
||||||
local f = function(x) return string.char(tonumber("0x"..x)) end
|
|
||||||
return (str:gsub("%+", " "):gsub("%%(..)", f))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.parseurl(url)
|
|
||||||
local res = {}
|
|
||||||
res.path, res.search = url:match("/([^%?]*)%??(.*)")
|
|
||||||
res.query = {}
|
|
||||||
for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
|
|
||||||
res.query[k] = lovebird.unescape(v)
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local htmlescapemap = {
|
|
||||||
["<"] = "<",
|
|
||||||
["&"] = "&",
|
|
||||||
['"'] = """,
|
|
||||||
["'"] = "'",
|
|
||||||
}
|
|
||||||
|
|
||||||
function lovebird.htmlescape(str)
|
|
||||||
return ( str:gsub("[<&\"']", htmlescapemap) )
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.truncate(str, len)
|
|
||||||
if #str <= len then
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
return str:sub(1, len - 3) .. "..."
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.compare(a, b)
|
|
||||||
local na, nb = tonumber(a), tonumber(b)
|
|
||||||
if na then
|
|
||||||
if nb then return na < nb end
|
|
||||||
return false
|
|
||||||
elseif nb then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return tostring(a) < tostring(b)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.checkwhitelist(addr)
|
|
||||||
if lovebird.whitelist == nil then return true end
|
|
||||||
for _, a in pairs(lovebird.whitelist) do
|
|
||||||
local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
|
|
||||||
if addr:match(ptn) then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.clear()
|
|
||||||
lovebird.lines = {}
|
|
||||||
lovebird.buffer = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.pushline(line)
|
|
||||||
line.time = os.time()
|
|
||||||
line.count = 1
|
|
||||||
table.insert(lovebird.lines, line)
|
|
||||||
if #lovebird.lines > lovebird.maxlines then
|
|
||||||
table.remove(lovebird.lines, 1)
|
|
||||||
end
|
|
||||||
lovebird.recalcbuffer()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.recalcbuffer()
|
|
||||||
local function doline(line)
|
|
||||||
local str = line.str
|
|
||||||
if not lovebird.allowhtml then
|
|
||||||
str = lovebird.htmlescape(line.str):gsub("\n", "<br>")
|
|
||||||
end
|
|
||||||
if line.type == "input" then
|
|
||||||
str = '<span class="inputline">' .. str .. '</span>'
|
|
||||||
else
|
|
||||||
if line.type == "error" then
|
|
||||||
str = '<span class="errormarker">!</span> ' .. str
|
|
||||||
str = '<span class="errorline">' .. str .. '</span>'
|
|
||||||
end
|
|
||||||
if line.count > 1 then
|
|
||||||
str = '<span class="repeatcount">' .. line.count .. '</span> ' .. str
|
|
||||||
end
|
|
||||||
if lovebird.timestamp then
|
|
||||||
str = os.date('<span class="timestamp">%H:%M:%S</span> ', line.time) ..
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "<br>")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.print(...)
|
|
||||||
local t = {}
|
|
||||||
for i = 1, select("#", ...) do
|
|
||||||
table.insert(t, tostring(select(i, ...)))
|
|
||||||
end
|
|
||||||
local str = table.concat(t, " ")
|
|
||||||
local last = lovebird.lines[#lovebird.lines]
|
|
||||||
if last and str == last.str then
|
|
||||||
-- Update last line if this line is a duplicate of it
|
|
||||||
last.time = os.time()
|
|
||||||
last.count = last.count + 1
|
|
||||||
lovebird.recalcbuffer()
|
|
||||||
else
|
|
||||||
-- Create new line
|
|
||||||
lovebird.pushline({ type = "output", str = str })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.onerror(err)
|
|
||||||
lovebird.pushline({ type = "error", str = err })
|
|
||||||
if lovebird.wrapprint then
|
|
||||||
lovebird.origprint("[lovebird] ERROR: " .. err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.onrequest(req, client)
|
|
||||||
local page = req.parsedurl.path
|
|
||||||
page = page ~= "" and page or "index"
|
|
||||||
-- Handle "page not found"
|
|
||||||
if not lovebird.pages[page] then
|
|
||||||
return "HTTP/1.1 404\r\nContent-Length: 8\r\n\r\nBad page"
|
|
||||||
end
|
|
||||||
-- Handle page
|
|
||||||
local str
|
|
||||||
xpcall(function()
|
|
||||||
local data = lovebird.pages[page](lovebird, req)
|
|
||||||
local contenttype = "text/html"
|
|
||||||
if string.match(page, "%.json$") then
|
|
||||||
contenttype = "application/json"
|
|
||||||
end
|
|
||||||
str = "HTTP/1.1 200 OK\r\n" ..
|
|
||||||
"Content-Type: " .. contenttype .. "\r\n" ..
|
|
||||||
"Content-Length: " .. #data .. "\r\n" ..
|
|
||||||
"\r\n" .. data
|
|
||||||
end, lovebird.onerror)
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.receive(client, pattern)
|
|
||||||
while 1 do
|
|
||||||
local data, msg = client:receive(pattern)
|
|
||||||
if not data then
|
|
||||||
if msg == "timeout" then
|
|
||||||
-- Wait for more data
|
|
||||||
coroutine.yield(true)
|
|
||||||
else
|
|
||||||
-- Disconnected -- yielding nil means we're done
|
|
||||||
coroutine.yield(nil)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.send(client, data)
|
|
||||||
local idx = 1
|
|
||||||
while idx < #data do
|
|
||||||
local res, msg = client:send(data, idx)
|
|
||||||
if not res and msg == "closed" then
|
|
||||||
-- Handle disconnect
|
|
||||||
coroutine.yield(nil)
|
|
||||||
else
|
|
||||||
idx = idx + res
|
|
||||||
coroutine.yield(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.onconnect(client)
|
|
||||||
-- Create request table
|
|
||||||
local requestptn = "(%S*)%s*(%S*)%s*(%S*)"
|
|
||||||
local req = {}
|
|
||||||
req.socket = client
|
|
||||||
req.addr, req.port = client:getsockname()
|
|
||||||
req.request = lovebird.receive(client, "*l")
|
|
||||||
req.method, req.url, req.proto = req.request:match(requestptn)
|
|
||||||
req.headers = {}
|
|
||||||
while 1 do
|
|
||||||
local line, msg = lovebird.receive(client, "*l")
|
|
||||||
if not line or #line == 0 then break end
|
|
||||||
local k, v = line:match("(.-):%s*(.*)$")
|
|
||||||
req.headers[k] = v
|
|
||||||
end
|
|
||||||
if req.headers["Content-Length"] then
|
|
||||||
req.body = lovebird.receive(client, req.headers["Content-Length"])
|
|
||||||
end
|
|
||||||
-- Parse body
|
|
||||||
req.parsedbody = {}
|
|
||||||
if req.body then
|
|
||||||
for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do
|
|
||||||
req.parsedbody[k] = lovebird.unescape(v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Parse request line's url
|
|
||||||
req.parsedurl = lovebird.parseurl(req.url)
|
|
||||||
-- Handle request; get data to send and send
|
|
||||||
local data = lovebird.onrequest(req)
|
|
||||||
lovebird.send(client, data)
|
|
||||||
-- Clear up
|
|
||||||
client:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function lovebird.update()
|
|
||||||
if not lovebird.inited then lovebird.init() end
|
|
||||||
-- Handle new connections
|
|
||||||
while 1 do
|
|
||||||
-- Accept new connections
|
|
||||||
local client = lovebird.server:accept()
|
|
||||||
if not client then break end
|
|
||||||
client:settimeout(0)
|
|
||||||
local addr = client:getsockname()
|
|
||||||
if lovebird.checkwhitelist(addr) then
|
|
||||||
-- Connection okay -- create and add coroutine to set
|
|
||||||
local conn = coroutine.wrap(function()
|
|
||||||
xpcall(function() lovebird.onconnect(client) end, function() end)
|
|
||||||
end)
|
|
||||||
lovebird.connections[conn] = true
|
|
||||||
else
|
|
||||||
-- Reject connection not on whitelist
|
|
||||||
lovebird.trace("got non-whitelisted connection attempt: ", addr)
|
|
||||||
client:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Handle existing connections
|
|
||||||
for conn in pairs(lovebird.connections) do
|
|
||||||
-- Resume coroutine, remove if it has finished
|
|
||||||
local status = conn()
|
|
||||||
if status == nil then
|
|
||||||
lovebird.connections[conn] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return lovebird
|
|
|
@ -1,128 +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
|
@ -1,303 +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 currentBody = body
|
|
||||||
|
|
||||||
if userdata.properties.dynamic == true then
|
|
||||||
currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic')
|
|
||||||
end
|
|
||||||
|
|
||||||
local fixture = love.physics.newFixture(currentBody, shape)
|
|
||||||
|
|
||||||
fixture:setUserData(userdata)
|
|
||||||
|
|
||||||
-- Set some custom properties from userdata (or use default set by box2d)
|
|
||||||
fixture:setFriction(userdata.properties.friction or 0.2)
|
|
||||||
fixture:setRestitution(userdata.properties.restitution or 0.0)
|
|
||||||
fixture:setSensor(userdata.properties.sensor or false)
|
|
||||||
fixture:setFilterData(userdata.properties.categories or 1,
|
|
||||||
userdata.properties.mask or 65535,
|
|
||||||
userdata.properties.group or 0)
|
|
||||||
|
|
||||||
local obj = {
|
|
||||||
object = object,
|
|
||||||
body = currentBody,
|
|
||||||
shape = shape,
|
|
||||||
fixture = fixture,
|
|
||||||
}
|
|
||||||
|
|
||||||
table.insert(collision, obj)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getPolygonVertices(object)
|
|
||||||
local vertices = {}
|
|
||||||
for _, vertex in ipairs(object.polygon) do
|
|
||||||
table.insert(vertices, vertex.x)
|
|
||||||
table.insert(vertices, vertex.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
return vertices
|
|
||||||
end
|
|
||||||
|
|
||||||
local function calculateObjectPosition(object, tile)
|
|
||||||
local o = {
|
|
||||||
shape = object.shape,
|
|
||||||
x = (object.dx or object.x) + map.offsetx,
|
|
||||||
y = (object.dy or object.y) + map.offsety,
|
|
||||||
w = object.width,
|
|
||||||
h = object.height,
|
|
||||||
polygon = object.polygon or object.polyline or object.ellipse or object.rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
local userdata = {
|
|
||||||
object = o,
|
|
||||||
properties = object.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = {obj.body:getWorldPoints(obj.shape:getPoints())}
|
|
||||||
local shape_type = obj.shape:getType()
|
|
||||||
|
|
||||||
if shape_type == "edge" or shape_type == "chain" then
|
|
||||||
love.graphics.line(points)
|
|
||||||
elseif shape_type == "polygon" then
|
|
||||||
love.graphics.polygon("line", points)
|
|
||||||
else
|
|
||||||
error("sti box2d plugin does not support "..shape_type.." shapes")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
lg.pop()
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Custom Properties in Tiled are used to tell this plugin what to do.
|
|
||||||
-- @table Properties
|
|
||||||
-- @field collidable set to true, can be used on any Layer, Tile, or Object
|
|
||||||
-- @field sensor set to true, can be used on any Tile or Object that is also collidable
|
|
||||||
-- @field dynamic set to true, can be used on any Tile or Object
|
|
||||||
-- @field friction can be used to define the friction of any Object
|
|
||||||
-- @field restitution can be used to define the restitution of any Object
|
|
||||||
-- @field categories can be used to set the filter Category of any Object
|
|
||||||
-- @field mask can be used to set the filter Mask of any Object
|
|
||||||
-- @field group can be used to set the filter Group of any Object
|
|
|
@ -1,194 +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'))
|
|
||||||
|
|
||||||
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 = {}
|
|
||||||
|
|
||||||
for _, tileset in ipairs(map.tilesets) do
|
|
||||||
for _, tile in ipairs(tileset.tiles) do
|
|
||||||
local gid = tileset.firstgid + tile.id
|
|
||||||
|
|
||||||
if map.tileInstances[gid] then
|
|
||||||
for _, instance in ipairs(map.tileInstances[gid]) do
|
|
||||||
-- Every object in every instance of a tile
|
|
||||||
if tile.objectGroup then
|
|
||||||
for _, object in ipairs(tile.objectGroup.objects) do
|
|
||||||
if object.properties.collidable == true then
|
|
||||||
local t = {
|
|
||||||
name = object.name,
|
|
||||||
type = object.type,
|
|
||||||
x = instance.x + map.offsetx + object.x,
|
|
||||||
y = instance.y + map.offsety + object.y,
|
|
||||||
width = object.width,
|
|
||||||
height = object.height,
|
|
||||||
layer = instance.layer,
|
|
||||||
properties = object.properties
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
world:add(t, t.x, t.y, t.width, t.height)
|
|
||||||
table.insert(collidables, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Every instance of a tile
|
|
||||||
if tile.properties and tile.properties.collidable == true then
|
|
||||||
local t = {
|
|
||||||
x = instance.x + map.offsetx,
|
|
||||||
y = instance.y + map.offsety,
|
|
||||||
width = map.tilewidth,
|
|
||||||
height = map.tileheight,
|
|
||||||
layer = instance.layer,
|
|
||||||
properties = tile.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
world:add(t, t.x, t.y, t.width, t.height)
|
|
||||||
table.insert(collidables, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, layer in ipairs(map.layers) do
|
|
||||||
-- Entire layer
|
|
||||||
if layer.properties.collidable == true then
|
|
||||||
if layer.type == "tilelayer" then
|
|
||||||
for y, tiles in ipairs(layer.data) do
|
|
||||||
for x, tile in pairs(tiles) do
|
|
||||||
|
|
||||||
if tile.objectGroup then
|
|
||||||
for _, object in ipairs(tile.objectGroup.objects) do
|
|
||||||
if object.properties.collidable == true then
|
|
||||||
local t = {
|
|
||||||
name = object.name,
|
|
||||||
type = object.type,
|
|
||||||
x = ((x-1) * map.tilewidth + tile.offset.x + map.offsetx) + object.x,
|
|
||||||
y = ((y-1) * map.tileheight + tile.offset.y + map.offsety) + object.y,
|
|
||||||
width = object.width,
|
|
||||||
height = object.height,
|
|
||||||
layer = layer,
|
|
||||||
properties = object.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
world:add(t, t.x, t.y, t.width, t.height)
|
|
||||||
table.insert(collidables, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local t = {
|
|
||||||
x = (x-1) * map.tilewidth + tile.offset.x + map.offsetx,
|
|
||||||
y = (y-1) * map.tileheight + tile.offset.y + map.offsety,
|
|
||||||
width = tile.width,
|
|
||||||
height = tile.height,
|
|
||||||
layer = layer,
|
|
||||||
properties = tile.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
world:add(t, t.x, t.y, t.width, t.height)
|
|
||||||
table.insert(collidables, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif layer.type == "imagelayer" then
|
|
||||||
world:add(layer, layer.x, layer.y, layer.width, layer.height)
|
|
||||||
table.insert(collidables, layer)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- individual collidable objects in a layer that is not "collidable"
|
|
||||||
-- or whole collidable objects layer
|
|
||||||
if layer.type == "objectgroup" then
|
|
||||||
for _, obj in ipairs(layer.objects) do
|
|
||||||
if layer.properties.collidable == true or obj.properties.collidable == true then
|
|
||||||
if obj.shape == "rectangle" then
|
|
||||||
local t = {
|
|
||||||
name = obj.name,
|
|
||||||
type = obj.type,
|
|
||||||
x = obj.x + map.offsetx,
|
|
||||||
y = obj.y + map.offsety,
|
|
||||||
width = obj.width,
|
|
||||||
height = obj.height,
|
|
||||||
layer = layer,
|
|
||||||
properties = obj.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.gid then
|
|
||||||
t.y = t.y - obj.height
|
|
||||||
end
|
|
||||||
|
|
||||||
world:add(t, t.x, t.y, t.width, t.height)
|
|
||||||
table.insert(collidables, t)
|
|
||||||
end -- TODO implement other object shapes?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
map.bump_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
|
|
||||||
}
|
|
||||||
|
|
||||||
--- 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,206 +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 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
|
|
|
@ -1,160 +0,0 @@
|
||||||
FlowBox = Menu:extend()
|
|
||||||
|
|
||||||
function FlowBox:new(x,y,w,h,slots_hor,slots_vert)
|
|
||||||
ListBox.super.new(self, x, y, w, h)
|
|
||||||
self.slots = slots_hor * slots_vert
|
|
||||||
self.slots_hor = slots_hor
|
|
||||||
self.slots_vert = slots_vert
|
|
||||||
self.begin = 1
|
|
||||||
self.widgetsH = math.floor( self.h / slots_vert )
|
|
||||||
self.widgetsW = math.floor( self.w / slots_hor )
|
|
||||||
self.h = slots_vert * self.widgetsH -- On fait en sorte que la hauteur
|
|
||||||
self.w = slots_hor * self.widgetsW -- et la largeur
|
|
||||||
-- soit un multiple du nombre de slot et de leur dimensions
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:update(dt)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local begincol, beginline = self:getCoord(self.begin)
|
|
||||||
|
|
||||||
if line < beginline then
|
|
||||||
beginline = line
|
|
||||||
end
|
|
||||||
|
|
||||||
if line > beginline + self.slots_vert - 1 then
|
|
||||||
beginline = line - self.slots_vert + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if beginline < 0 then
|
|
||||||
beginline = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
self.begin = beginline * self.slots_hor + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:getCoord(id_selected)
|
|
||||||
id_selected = id_selected - 1 -- On simplifie les calcul en prenant 0 comme départ
|
|
||||||
local line, col
|
|
||||||
line = math.floor(id_selected / self.slots_hor)
|
|
||||||
col = id_selected - (line * self.slots_hor)
|
|
||||||
return col, line
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:moveCursor(new_col, new_line)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local lastcol, lastline = self:getCoord(#self.listWidget)
|
|
||||||
|
|
||||||
|
|
||||||
if new_line < 0 then
|
|
||||||
new_line = lastline
|
|
||||||
end
|
|
||||||
|
|
||||||
if new_line > lastline then
|
|
||||||
new_line = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
if (new_line == lastline) then
|
|
||||||
if new_col < 0 then
|
|
||||||
new_col = lastcol
|
|
||||||
end
|
|
||||||
|
|
||||||
if new_col > lastcol then
|
|
||||||
if (line == lastline) then
|
|
||||||
new_col = 0
|
|
||||||
else
|
|
||||||
new_col = lastcol
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if new_col < 0 then
|
|
||||||
new_col = self.slots_hor - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if new_col == self.slots_hor then
|
|
||||||
new_col = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.selected = (new_line * self.slots_hor) + new_col + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:keyreleased(key, code)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
if key == 'left' then
|
|
||||||
self:moveCursor(col - 1, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'right' then
|
|
||||||
self:moveCursor(col + 1, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'up' then
|
|
||||||
self:moveCursor(col, line - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'down' then
|
|
||||||
self:moveCursor(col, line + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "A" then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:mousemoved(x, y)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local begincol, beginline = self:getCoord(self.begin)
|
|
||||||
local newcol, newline
|
|
||||||
|
|
||||||
newline = beginline + math.floor(y / self.widgetsH)
|
|
||||||
newcol = math.floor(x / self.widgetsW)
|
|
||||||
self.selected = (newline * self.slots_hor) + newcol + 1
|
|
||||||
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:mousepressed(x, y, button, isTouch)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local begincol, beginline = self:getCoord(self.begin)
|
|
||||||
local newline, newcol
|
|
||||||
|
|
||||||
newline = beginline + math.floor(y / self.widgetsH)
|
|
||||||
newcol = math.floor(x / self.widgetsW)
|
|
||||||
self.selected = (newline * self.slots_hor) + newcol + 1
|
|
||||||
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
|
|
||||||
if #self.listWidget > 0 then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function FlowBox:draw()
|
|
||||||
local widgety = self.y
|
|
||||||
local widgetx = self.x
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
if (i >= self.begin) and (i < self.begin + self.slots) then
|
|
||||||
v:draw(widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
if self.selected == i and self.focus == true then
|
|
||||||
v:drawSelected(widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
else
|
|
||||||
v:draw(widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
end
|
|
||||||
widgetx = widgetx + self.widgetsW
|
|
||||||
if widgetx == (self.x + self.w) then
|
|
||||||
widgetx = self.x
|
|
||||||
widgety = widgety + self.widgetsH
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,227 +0,0 @@
|
||||||
GridBox = Menu:extend()
|
|
||||||
|
|
||||||
function GridBox:new(x,y,w,h,slots_hor,slots_vert)
|
|
||||||
ListBox.super.new(self, x, y, w, h)
|
|
||||||
self.slots = slots_hor * slots_vert
|
|
||||||
self.slots_hor = slots_hor
|
|
||||||
self.slots_vert = slots_vert
|
|
||||||
self.begin = 1
|
|
||||||
self.widgetsH = math.floor( self.h / slots_vert )
|
|
||||||
self.widgetsW = math.floor( self.w / slots_hor )
|
|
||||||
self.h = slots_vert * self.widgetsH -- On fait en sorte que la hauteur
|
|
||||||
self.w = slots_hor * self.widgetsW -- et la largeur
|
|
||||||
-- soit un multiple du nombre de slot et de leur dimensions
|
|
||||||
self.cursor = {}
|
|
||||||
self.cursor.x = 0
|
|
||||||
self.cursor.y = 0
|
|
||||||
|
|
||||||
-- La gridbox possède la particularité de pouvoir fusioner des slots, on fait
|
|
||||||
-- donc une liste de slots disponibles, qui serviront par la suite.
|
|
||||||
self.listSlot = {}
|
|
||||||
for i= 1, self.slots do
|
|
||||||
self.listSlot[i] = {}
|
|
||||||
self.listSlot[i].sizeH = 1
|
|
||||||
self.listSlot[i].sizeW = 1
|
|
||||||
self.listSlot[i].isSlave = 0
|
|
||||||
self.listSlot[i].widgetID = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:update(dt)
|
|
||||||
self.begin = 1
|
|
||||||
local slotID = self:getSlotbyCoord(self.cursor.x, self.cursor.y)
|
|
||||||
if self.listSlot[slotID].isSlave > 0 then
|
|
||||||
slotID = self.listSlot[slotID].isSlave
|
|
||||||
end
|
|
||||||
self.selected = self.listSlot[slotID].widgetID
|
|
||||||
self.cursor.x, self.cursor.y = self:getCoord(slotID)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:regenSlots()
|
|
||||||
local widgetID = 1
|
|
||||||
for i,v in ipairs(self.listSlot) do
|
|
||||||
if v.isSlave == 0 and (widgetID <= #self.listWidget) then
|
|
||||||
self.listSlot[i].widgetID = widgetID
|
|
||||||
widgetID = widgetID + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:addCol(slotID)
|
|
||||||
local col, line = self:getCoord(slotID)
|
|
||||||
if (col + self.listSlot[slotID].sizeW + 1) <= self.slots_hor then
|
|
||||||
slotSlave = slotID + self.listSlot[slotID].sizeW
|
|
||||||
for i = 1, self.listSlot[slotID].sizeH do
|
|
||||||
self.listSlot[slotSlave + ((i-1)* self.slots_hor)].isSlave = slotID
|
|
||||||
end
|
|
||||||
self.listSlot[slotID].sizeW = self.listSlot[slotID].sizeW + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:addLine(slotID)
|
|
||||||
local col, line = self:getCoord(slotID)
|
|
||||||
if (line + self.listSlot[slotID].sizeH + 1) <= self.slots_vert then
|
|
||||||
slotSlave = slotID + (self.listSlot[slotID].sizeH * self.slots_hor)
|
|
||||||
for i = 1, self.listSlot[slotID].sizeW do
|
|
||||||
self.listSlot[slotSlave + (i-1)].isSlave = slotID
|
|
||||||
end
|
|
||||||
self.listSlot[slotID].sizeH = self.listSlot[slotID].sizeH + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:getCoord(id_selected)
|
|
||||||
id_selected = id_selected - 1 -- On simplifie les calcul en prenant 0 comme départ
|
|
||||||
local line, col
|
|
||||||
line = math.floor(id_selected / self.slots_hor)
|
|
||||||
col = id_selected - (line * self.slots_hor)
|
|
||||||
return col, line
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:getSlotbyCoord(col, line)
|
|
||||||
return (line * self.slots_hor) + col + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:getSlot(widgetID)
|
|
||||||
local slotID
|
|
||||||
for i,v in ipairs(self.listSlot) do
|
|
||||||
if v.widgetID == widgetID then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:moveCursor(newcol, newline)
|
|
||||||
local col, line = self.cursor.x, self.cursor.y
|
|
||||||
local relcol, relline = newcol - col, newline - line
|
|
||||||
self.cursor.x, self.cursor.y = newcol, newline
|
|
||||||
|
|
||||||
while self.cursor.y < 0 do
|
|
||||||
self.cursor.y = self.cursor.y + self.slots_vert
|
|
||||||
end
|
|
||||||
|
|
||||||
while self.cursor.x < 0 do
|
|
||||||
self.cursor.x = self.cursor.x + self.slots_hor
|
|
||||||
end
|
|
||||||
|
|
||||||
while self.cursor.y >= self.slots_vert do
|
|
||||||
self.cursor.y = self.cursor.y - self.slots_vert
|
|
||||||
end
|
|
||||||
|
|
||||||
while self.cursor.x >= self.slots_hor do
|
|
||||||
self.cursor.x = self.cursor.x - self.slots_hor
|
|
||||||
end
|
|
||||||
previousSlot = self:getSlotbyCoord(col, line)
|
|
||||||
newSlot = self:getSlotbyCoord(self.cursor.x, self.cursor.y)
|
|
||||||
|
|
||||||
if (self.listSlot[newSlot].isSlave > 0) or (self.listSlot[newSlot].widgetID > #self.listWidget) then
|
|
||||||
if (self.listSlot[newSlot].isSlave == previousSlot) or (self.listSlot[newSlot].widgetID > #self.listWidget) then
|
|
||||||
self:moveCursor(self.cursor.x + relcol, self.cursor.y + relline)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:keyreleased(key, code)
|
|
||||||
slotID = self:getSlot(self.selected)
|
|
||||||
local col, line = self.cursor.x, self.cursor.y
|
|
||||||
if key == 'left' then
|
|
||||||
--self:moveCol(-1)
|
|
||||||
self:moveCursor(col - 1, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'right' then
|
|
||||||
--self:moveCol(1)
|
|
||||||
self:moveCursor(col + 1, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'up' then
|
|
||||||
self:moveCursor(col, line - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'down' then
|
|
||||||
self:moveCursor(col, line + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "A" and self.selected <= #self.listWidget then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:mousemoved(x, y)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local begincol, beginline = self:getCoord(self.begin)
|
|
||||||
local newcol, newline
|
|
||||||
local newselect, slotID
|
|
||||||
|
|
||||||
newline = beginline + math.floor(y / self.widgetsH)
|
|
||||||
newcol = math.floor(x / self.widgetsW)
|
|
||||||
self.cursor.x = newcol
|
|
||||||
self.cursor.y = newline
|
|
||||||
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:mousepressed(x, y, button, isTouch)
|
|
||||||
local col, line = self:getCoord(self.selected)
|
|
||||||
local begincol, beginline = self:getCoord(self.begin)
|
|
||||||
local newcol, newline
|
|
||||||
local newselect, slotID
|
|
||||||
|
|
||||||
newline = beginline + math.floor(y / self.widgetsH)
|
|
||||||
newcol = math.floor(x / self.widgetsW)
|
|
||||||
newselect = (newline * self.slots_hor) + newcol + 1
|
|
||||||
|
|
||||||
if self.listSlot[newselect].isSlave > 0 then
|
|
||||||
slotID = self.listSlot[newselect].isSlave
|
|
||||||
else
|
|
||||||
slotID = newselect
|
|
||||||
end
|
|
||||||
|
|
||||||
self.selected = self.listSlot[slotID].widgetID
|
|
||||||
|
|
||||||
if #self.listWidget > 0 and self.selected > 1 and self.selected <= #self.listWidget then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function GridBox:draw()
|
|
||||||
local widgety = self.y
|
|
||||||
local widgetx = self.x
|
|
||||||
|
|
||||||
self:regenSlots() -- On reget les slots au cas où :p
|
|
||||||
for i,v in ipairs(self.listSlot) do
|
|
||||||
if (v.isSlave == 0) and (v.widgetID <= #self.listWidget) then
|
|
||||||
--self.listWidget[v.widgetID]:draw(widgetx, widgety, self.widgetsW * v.sizeW, self.widgetsH * v.sizeH)
|
|
||||||
if self.selected == v.widgetID and self.focus == true then
|
|
||||||
self.listWidget[v.widgetID]:drawSelected(widgetx, widgety, self.widgetsW * v.sizeW, self.widgetsH * v.sizeH)
|
|
||||||
else
|
|
||||||
self.listWidget[v.widgetID]:draw(widgetx, widgety, self.widgetsW * v.sizeW, self.widgetsH * v.sizeH)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (v.isSlave > 0) and false then
|
|
||||||
love.graphics.setColor(255,255,255,128)
|
|
||||||
love.graphics.rectangle("fill", widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
end
|
|
||||||
local col, line = self:getCoord(i)
|
|
||||||
|
|
||||||
if (col == self.cursor.x) and (line == self.cursor.y) and false then
|
|
||||||
love.graphics.setColor(255,255,0,128)
|
|
||||||
love.graphics.rectangle("fill", widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
end
|
|
||||||
|
|
||||||
--love.graphics.setColor(0,0,0,10)
|
|
||||||
--love.graphics.rectangle("line", widgetx, widgety, self.widgetsW, self.widgetsH)
|
|
||||||
widgetx = widgetx + self.widgetsW
|
|
||||||
if widgetx == (self.x + self.w) then
|
|
||||||
widgetx = self.x
|
|
||||||
widgety = widgety + self.widgetsH
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,86 +0,0 @@
|
||||||
MenuController = Object:extend()
|
|
||||||
|
|
||||||
require "modules.menus.menu"
|
|
||||||
require "modules.menus.widgets"
|
|
||||||
require "modules.menus.listbox"
|
|
||||||
require "modules.menus.flowbox"
|
|
||||||
require "modules.menus.grid"
|
|
||||||
require "modules.menus.textmenu"
|
|
||||||
|
|
||||||
|
|
||||||
function MenuController:new()
|
|
||||||
self.menus = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:reset()
|
|
||||||
self.menus = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:addMenu(menu)
|
|
||||||
table.insert(self.menus, menu)
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:update()
|
|
||||||
self:clear()
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
v.id = i
|
|
||||||
v:update(dt)
|
|
||||||
v:updateWidgets(dt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:clear()
|
|
||||||
-- On retire les entitées marquées comme supprimées
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
if (v.destroyed == true) then
|
|
||||||
table.remove(self.menus, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:updateList()
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
v.id = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:keyreleased(key, code)
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
if v.focus == true then
|
|
||||||
v:keyreleased(key, code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:mousemoved(x, y, dx, dy)
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
if (x > v.x) and (x < v.x + v.w) and (y > v.y) and (y < v.y + v.h) then
|
|
||||||
v:mousemoved(x - v.x, y - v.y)
|
|
||||||
for j,u in ipairs(self.menus) do
|
|
||||||
u.focus = false
|
|
||||||
end
|
|
||||||
v.focus = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:mousepressed( x, y, button, istouch )
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
if (x > v.x) and (x < v.x + v.w) and (y > v.y) and (y < v.y + v.h) then
|
|
||||||
v:mousepressed(x - v.x, y - v.y, button, istouch )
|
|
||||||
for j,u in ipairs(self.menus) do
|
|
||||||
u.focus = false
|
|
||||||
end
|
|
||||||
v.focus = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MenuController:draw(dt) -- On dessine les entitées
|
|
||||||
for i,v in ipairs(self.menus) do
|
|
||||||
v.id = i
|
|
||||||
v:draw(dt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return MenuController
|
|
|
@ -1,77 +0,0 @@
|
||||||
ListBox = Menu:extend()
|
|
||||||
|
|
||||||
function ListBox:new(x,y,w,h,slots)
|
|
||||||
ListBox.super.new(self, x, y, w, h)
|
|
||||||
self.slots = slots
|
|
||||||
self.begin = 1
|
|
||||||
self.widgetsH = math.floor( self.h / slots )
|
|
||||||
self.h = slots * self.widgetsH -- On fait en sorte que la hauteur
|
|
||||||
-- soit un multiple du nombre de slot et de leur hauteur
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListBox:update(dt)
|
|
||||||
if self.selected < self.begin then
|
|
||||||
self.begin = self.selected
|
|
||||||
end
|
|
||||||
if self.selected > self.begin + self.slots - 1 then
|
|
||||||
self.begin = self.selected - self.slots + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.begin < 1 then
|
|
||||||
self.begin = 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListBox:keyreleased(key, code)
|
|
||||||
|
|
||||||
if key == 'up' then
|
|
||||||
self:moveCursor(self.selected - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'down' then
|
|
||||||
self:moveCursor(self.selected + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "A" then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListBox:mousemoved(x, y)
|
|
||||||
self.selected = self.begin + math.floor(y / self.widgetsH)
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListBox:mousepressed(x, y, button, isTouch)
|
|
||||||
self.selected = self.begin + math.floor(y / self.widgetsH)
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
if #self.listWidget > 0 then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ListBox:draw()
|
|
||||||
local widgety = self.y
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
if (i >= self.begin) and (i < self.begin + self.slots) then
|
|
||||||
v:draw(self.x, widgety, self.w, self.widgetsH)
|
|
||||||
if self.selected == i and self.focus == true then
|
|
||||||
v:drawSelected(self.x, widgety, self.w, self.widgetsH)
|
|
||||||
else
|
|
||||||
v:draw(self.x, widgety, self.w, self.widgetsH)
|
|
||||||
end
|
|
||||||
widgety = widgety + self.widgetsH
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,97 +0,0 @@
|
||||||
Menu = Object:extend()
|
|
||||||
|
|
||||||
function Menu:new(x,y,w,h)
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.w = w
|
|
||||||
self.h = h
|
|
||||||
self.listWidget = {}
|
|
||||||
self.selected = 0
|
|
||||||
self.selectedPrevious = 0
|
|
||||||
|
|
||||||
self.destroyed = false
|
|
||||||
self.focus = false
|
|
||||||
self.cancel = 0
|
|
||||||
self.virtualpad = vpad()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:setLastWidgetCancel()
|
|
||||||
self.cancel = #self.listWidget
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:cancelAction()
|
|
||||||
if (self.cancel ~= 0) then
|
|
||||||
self.listWidget[self.cancel]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:update(dt)
|
|
||||||
-- Cette fonction ne contient rien par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:empty()
|
|
||||||
self.listWidget = {}
|
|
||||||
self.cancel = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:resize(x,y,w,h)
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.w = w
|
|
||||||
self.h = h
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:destroy()
|
|
||||||
self.destroyed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:draw()
|
|
||||||
-- Cette fonction ne contient rien par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:keyreleased(key, code)
|
|
||||||
-- Cette fonction ne contient rien par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:mousemoved(x, y)
|
|
||||||
-- Cette fonction ne contient rien par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:mousepressed( x, y, button, istouch )
|
|
||||||
-- Cette fonction ne contient rien par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:addWidget(newwidget)
|
|
||||||
if #self.listWidget == 0 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
table.insert(self.listWidget, newwidget)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:updateWidgets(dt)
|
|
||||||
self:clearWidgets()
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
v.id = i
|
|
||||||
v:update(dt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:clearWidgets() -- On retire les widgets marquées comme supprimées
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
if (v.destroyed == true) then
|
|
||||||
table.remove(self.listWidget, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Menu:moveCursor(new_selected)
|
|
||||||
if new_selected < 1 then
|
|
||||||
self.selected = #self.listWidget + new_selected
|
|
||||||
else
|
|
||||||
if new_selected > #self.listWidget then
|
|
||||||
self.selected = new_selected - #self.listWidget
|
|
||||||
else
|
|
||||||
self.selected = new_selected
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,111 +0,0 @@
|
||||||
TextMenu = Menu:extend()
|
|
||||||
|
|
||||||
function TextMenu:new(x, y, font, slots)
|
|
||||||
TextMenu.super.new(self, x, y, 0, 0)
|
|
||||||
self.font = assets:getFont(font)
|
|
||||||
self.center = false
|
|
||||||
self.widgetsH = self.font:getHeight()
|
|
||||||
self.slots = slots
|
|
||||||
self.h = self.widgetsH * self.slots
|
|
||||||
self.begin = 1
|
|
||||||
self.fixedWidth = false
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:centerText(width)
|
|
||||||
self.fixedWidth = true
|
|
||||||
self.center = true
|
|
||||||
self.w = width
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:update(dt)
|
|
||||||
if (self.fixedWidth == false) then
|
|
||||||
self:getWidth()
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.selected < self.begin then
|
|
||||||
self.begin = self.selected
|
|
||||||
end
|
|
||||||
if self.selected > self.begin + self.slots - 1 then
|
|
||||||
self.begin = self.selected - self.slots + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.begin < 1 then
|
|
||||||
self.begin = 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:getWidth()
|
|
||||||
local width = self.w
|
|
||||||
|
|
||||||
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
local stringWidth = self.font:getWidth(v.label)
|
|
||||||
width = math.max(stringWidth, width)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.w = width
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:keyreleased(key, code)
|
|
||||||
key = self.virtualpad:translateAction(1, key)
|
|
||||||
|
|
||||||
if key == 'up' then
|
|
||||||
self:moveCursor(self.selected - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == 'down' then
|
|
||||||
self:moveCursor(self.selected + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "A" then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "B" then
|
|
||||||
self:cancelAction()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:mousemoved(x, y)
|
|
||||||
self.selected = self.begin + math.floor(y / self.widgetsH)
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:mousepressed(x, y, button, isTouch)
|
|
||||||
self.selected = self.begin + math.floor(y / self.widgetsH)
|
|
||||||
if self.selected < 1 then
|
|
||||||
self.selected = 1
|
|
||||||
end
|
|
||||||
if self.selected > #self.listWidget then
|
|
||||||
self.selected = #self.listWidget
|
|
||||||
end
|
|
||||||
if #self.listWidget > 0 then
|
|
||||||
self.listWidget[self.selected]:action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextMenu:draw()
|
|
||||||
local widgety = self.y
|
|
||||||
self.font:set()
|
|
||||||
for i,v in ipairs(self.listWidget) do
|
|
||||||
if (i >= self.begin) and (i < self.begin + self.slots) then
|
|
||||||
if self.selected == i and self.focus == true then
|
|
||||||
love.graphics.setColor(85, 170, 255)
|
|
||||||
else
|
|
||||||
utils.graphics.resetColor()
|
|
||||||
end
|
|
||||||
if (self.center) then
|
|
||||||
love.graphics.printf(v.label, self.x, widgety, self.w, "center")
|
|
||||||
else
|
|
||||||
love.graphics.print(v.label, self.x, widgety)
|
|
||||||
end
|
|
||||||
widgety = widgety + self.widgetsH
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,114 +0,0 @@
|
||||||
Widget = Object:extend()
|
|
||||||
DummyWidget = Widget:extend()
|
|
||||||
|
|
||||||
function Widget:new()
|
|
||||||
self.destroyed = false
|
|
||||||
self.selectable = false
|
|
||||||
self.selection_margin = 0
|
|
||||||
self.margin = 2
|
|
||||||
self.label = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:selectAction()
|
|
||||||
-- Do nothing
|
|
||||||
end
|
|
||||||
|
|
||||||
function DummyWidget:new()
|
|
||||||
DummyWidget.super.new(self)
|
|
||||||
self.r = love.math.random(128)
|
|
||||||
self.g = love.math.random(128)
|
|
||||||
self.b = love.math.random(128)
|
|
||||||
self.selectable = true
|
|
||||||
self.label = "DUMMY WIDGET (see modules.menus.widget)"
|
|
||||||
end
|
|
||||||
|
|
||||||
function DummyWidget:draw(x, y, w, h)
|
|
||||||
x = x + self.margin
|
|
||||||
y = y + self.margin
|
|
||||||
w = w - self.margin * 2
|
|
||||||
h = h - self.margin * 2
|
|
||||||
|
|
||||||
love.graphics.setColor(self.r,self.g,self.b,70)
|
|
||||||
love.graphics.rectangle("fill", x, y, w, h)
|
|
||||||
love.graphics.setColor(self.r,self.g,self.b)
|
|
||||||
love.graphics.rectangle("line", x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:drawSelected(x,y,w,h)
|
|
||||||
self:draw(x, y, w, h)
|
|
||||||
x = x + self.selection_margin
|
|
||||||
y = y + self.selection_margin
|
|
||||||
w = w - self.selection_margin * 2
|
|
||||||
h = h - self.selection_margin * 2
|
|
||||||
|
|
||||||
love.graphics.setColor(0,0,0)
|
|
||||||
love.graphics.rectangle("fill", x, y, 4, 8)
|
|
||||||
love.graphics.rectangle("fill", x, y, 8, 4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w, y, -4, 8)
|
|
||||||
love.graphics.rectangle("fill", x + w, y, -8, 4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x, y + h, 4, -8)
|
|
||||||
love.graphics.rectangle("fill", x, y + h, 8, -4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w, y + h, -4, -8)
|
|
||||||
love.graphics.rectangle("fill", x + w, y + h, -8, -4)
|
|
||||||
|
|
||||||
love.graphics.setColor(255,255,255)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + 1, 2, 6)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + 1, 6, 2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + 1, -2, 6)
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + 1, -6, 2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + h - 1, 2, -6)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + h - 1, 6, -2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + h - 1, -2, -6)
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + h - 1, -6, -2)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:draw(x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:update(dt)
|
|
||||||
-- N/A
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:action()
|
|
||||||
self:destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Widget:destroy()
|
|
||||||
self.destroyed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function drawWidget_selected(x, y, w, h)
|
|
||||||
love.graphics.setColor(0,0,0)
|
|
||||||
love.graphics.rectangle("fill", x, y, 4, 8)
|
|
||||||
love.graphics.rectangle("fill", x, y, 8, 4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w, y, -4, 8)
|
|
||||||
love.graphics.rectangle("fill", x + w, y, -8, 4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x, y + h, 4, -8)
|
|
||||||
love.graphics.rectangle("fill", x, y + h, 8, -4)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w, y + h, -4, -8)
|
|
||||||
love.graphics.rectangle("fill", x + w, y + h, -8, -4)
|
|
||||||
|
|
||||||
love.graphics.setColor(255,255,255)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + 1, 2, 6)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + 1, 6, 2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + 1, -2, 6)
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + 1, -6, 2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + h - 1, 2, -6)
|
|
||||||
love.graphics.rectangle("fill", x + 1, y + h - 1, 6, -2)
|
|
||||||
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + h - 1, -2, -6)
|
|
||||||
love.graphics.rectangle("fill", x + w - 1, y + h - 1, -6, -2)
|
|
||||||
|
|
||||||
end
|
|
|
@ -1,60 +0,0 @@
|
||||||
Savegame = Object:extend()
|
|
||||||
|
|
||||||
local binser = require "libs.binser"
|
|
||||||
|
|
||||||
require "modules.savegame.pigs"
|
|
||||||
|
|
||||||
function Savegame:new(filename)
|
|
||||||
self:reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:init(filename)
|
|
||||||
self:reset()
|
|
||||||
self.name = filename
|
|
||||||
self.slot = 1 -- le slot 0 signifie qu'on ne peut pas sauvegarder.
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:reload()
|
|
||||||
self:read(self.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:getSaveFile(saveslot, absolute)
|
|
||||||
local dir = ""
|
|
||||||
if absolute then
|
|
||||||
dir = love.filesystem.getSaveDirectory() .. "/"
|
|
||||||
if not love.filesystem.exists(dir) then
|
|
||||||
love.filesystem.createDirectory( "" )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local filepath = dir .. "save" .. saveslot .. ".save"
|
|
||||||
|
|
||||||
return filepath
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:read()
|
|
||||||
if self.slot > 0 then
|
|
||||||
filepath = self:getSaveFile(self.slot, true)
|
|
||||||
if love.filesystem.exists("save" .. self.slot .. ".save") then
|
|
||||||
local loadedDatas = binser.readFile(filepath)
|
|
||||||
self.data = loadedDatas[1]
|
|
||||||
else
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:write()
|
|
||||||
if self.slot > 0 then
|
|
||||||
filepath = self:getSaveFile(self.slot, true)
|
|
||||||
binser.writeFile(filepath, self.data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:reset()
|
|
||||||
self.name = ""
|
|
||||||
self.data = {}
|
|
||||||
self.slot = 1
|
|
||||||
self:resetPigs()
|
|
||||||
end
|
|
||||||
|
|
||||||
return Savegame
|
|
|
@ -1,9 +0,0 @@
|
||||||
local defaultKeyboard = require "datas.keyboard"
|
|
||||||
|
|
||||||
function Savegame:initKeyboard()
|
|
||||||
self.save.keyboard = defaultKeyboard
|
|
||||||
end
|
|
||||||
|
|
||||||
function Savegame:getKeyboard(playerID)
|
|
||||||
return self.save.keyboard[playerID]
|
|
||||||
end
|
|
|
@ -1,153 +0,0 @@
|
||||||
local CameraSystem = Object:extend()
|
|
||||||
local Camera = require "libs.hump.camera"
|
|
||||||
|
|
||||||
-- INIT FUNCTIONS
|
|
||||||
-- Initialize the camera system
|
|
||||||
|
|
||||||
function CameraSystem:new(scene, x, y)
|
|
||||||
self.scene = scene
|
|
||||||
self.world = scene.world
|
|
||||||
self.view = Camera(x, y, 1, 0, true)
|
|
||||||
|
|
||||||
local width, height, flags = love.window.getMode( )
|
|
||||||
self.screenWidth = width
|
|
||||||
self.screenHeight = height
|
|
||||||
self.viewWidth, self.viewHeight = core.screen:getDimensions()
|
|
||||||
|
|
||||||
self.resolution = self.screenWidth / self.viewWidth
|
|
||||||
self:limit()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- WRAPPER and UTILS
|
|
||||||
-- Access data from the camera
|
|
||||||
|
|
||||||
function CameraSystem:floorCoord()
|
|
||||||
self.view.x, self.view.y = utils.math.floorCoord(self.view.x, self.view.y)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:getCoord()
|
|
||||||
local camx, camy, camh, camw
|
|
||||||
camx = self.view.x - (self.viewWidth/2)
|
|
||||||
camy = self.view.y - (self.viewHeight/2)
|
|
||||||
|
|
||||||
camw = self.viewWidth
|
|
||||||
camh = self.viewHeight
|
|
||||||
return camx, camy, camw, camh
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:getScale()
|
|
||||||
return self.view.scale
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:getScreenCoord()
|
|
||||||
local camx, camy, camh, camw
|
|
||||||
camx = self.view.x - (self.screenWidth/2)
|
|
||||||
camy = self.view.y - (self.screenHeight/2)
|
|
||||||
|
|
||||||
camw = self.screenWidth
|
|
||||||
camh = self.screenHeight
|
|
||||||
return camx, camy, camw, camh
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:worldCoord(x, y, ox, oy, w, h)
|
|
||||||
ox, oy = ox or 0, oy or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
return self.view:worldCoords(x, y, ox, oy, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:cameraCoords(x, y)
|
|
||||||
return self.view:cameraCoords(x, y, ox, oy, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:getVisibleEntities()
|
|
||||||
local camx, camy, camw, camh = self:getScreenCoord()
|
|
||||||
local visibleThings, len = self.world:queryRect(camx-64, camy-64, camw+128, camh+128)
|
|
||||||
return visibleThings, len
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:isInsideView(x, y, w, h, border)
|
|
||||||
local camx, camy, camw, camh = self:getCoord()
|
|
||||||
local border = border or 0
|
|
||||||
if (x + w + border >= camx) and (x < camx + camw + border)
|
|
||||||
and (y + h + border >= camy) and (y < camy + camh + border) then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:attach()
|
|
||||||
self.view:attach()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:detach()
|
|
||||||
self.view:detach()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- UPDATE and MOVE functions
|
|
||||||
-- Move and update the camera system
|
|
||||||
|
|
||||||
function CameraSystem:update(dt)
|
|
||||||
self:followPlayer(self.scene.playermanager.activePlayer)
|
|
||||||
self:limit()
|
|
||||||
self:floorCoord()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:followPlayer(id)
|
|
||||||
local player = self.scene.playermanager:getPlayerByID(id)
|
|
||||||
|
|
||||||
if (player ~= nil) then
|
|
||||||
local playx, playy = utils.math.floorCoord(player.center.x,
|
|
||||||
player.center.y)
|
|
||||||
|
|
||||||
local camx, camy = self.view.x + (self.viewWidth/2),
|
|
||||||
self.view.y + (self.viewHeight/2)
|
|
||||||
|
|
||||||
|
|
||||||
playx, playy = self:cameraCoords(playx, playy)
|
|
||||||
playx = playx - (self.viewWidth/2)
|
|
||||||
playy = playy - (self.viewHeight/2)
|
|
||||||
|
|
||||||
if (math.abs(playx) > 8) then
|
|
||||||
camx = camx + (playx - (8*utils.math.sign(playx)))
|
|
||||||
end
|
|
||||||
|
|
||||||
if (playy > 16) then
|
|
||||||
camy = camy + (playy - 16)
|
|
||||||
elseif (playy < -64) then
|
|
||||||
camy = camy + (playy + 64)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.view.x, self.view.y = camx - (self.viewWidth/2),
|
|
||||||
camy - (self.viewHeight/2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:move(x, y)
|
|
||||||
self.view.x = x
|
|
||||||
self.view.y = y
|
|
||||||
|
|
||||||
self:limit()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CameraSystem:limit()
|
|
||||||
local camx, camy = self.view.x, self.view.y
|
|
||||||
local camMinX, camMinY = (self.screenWidth/2), (self.screenHeight/2)
|
|
||||||
|
|
||||||
local camMaxX, camMaxY = self.world:getDimensions()
|
|
||||||
|
|
||||||
if (self.resolution == 1) then
|
|
||||||
camMaxX, camMaxY = camMaxX - camMinX, camMaxY - camMinY
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
camx = math.max(camx, camMinX)
|
|
||||||
camx = math.min(camx, camMaxX)
|
|
||||||
|
|
||||||
camy = math.max(camy, camMinY)
|
|
||||||
camy = math.min(camy, camMaxY)
|
|
||||||
|
|
||||||
self.view.x, self.view.y = camx, camy
|
|
||||||
end
|
|
||||||
|
|
||||||
return CameraSystem
|
|
|
@ -1,92 +0,0 @@
|
||||||
local PlayerManager = Object:extend()
|
|
||||||
local Obj = require "scenes.levels.entities"
|
|
||||||
|
|
||||||
function PlayerManager:new(scene)
|
|
||||||
self.scene = scene
|
|
||||||
self.players = {}
|
|
||||||
self.startx, self.starty = self.scene.world:getStartPosition()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- PLAYER FUNCTIONS
|
|
||||||
-- Handle virtual players
|
|
||||||
|
|
||||||
-- TODO: Gérer la manière dont le joueur va avoir une équipe de cochons
|
|
||||||
|
|
||||||
function PlayerManager:addPlayer(pigID)
|
|
||||||
-- Enregistrer le joueur n'est pas le rajouter à une liste des objets qui existe,
|
|
||||||
-- mais juste insérer ses informations les plus importantes afin d'aider le jeu
|
|
||||||
-- à pouvoir le reconstruire.
|
|
||||||
local play = {}
|
|
||||||
play.pigID = pigID
|
|
||||||
play.isDead = 0
|
|
||||||
|
|
||||||
table.insert(self.players, play)
|
|
||||||
end
|
|
||||||
|
|
||||||
function PlayerManager:spawnPlayer(playerID)
|
|
||||||
local play = self.players[playerID]
|
|
||||||
--Obj.Player(self.scene.world, self.startx, self.starty, playerID)
|
|
||||||
--self.activePlayer = playerID
|
|
||||||
end
|
|
||||||
|
|
||||||
function PlayerManager:getPlayers()
|
|
||||||
local itemList = self.scene.world:getActors()
|
|
||||||
local playerList = {}
|
|
||||||
|
|
||||||
for i,v in ipairs(itemList) do
|
|
||||||
if (v.playerID > 0) then
|
|
||||||
table.insert(playerList, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return playerList
|
|
||||||
end
|
|
||||||
|
|
||||||
function PlayerManager:getPlayerByID(id)
|
|
||||||
local itemList = self.scene.world:getActors()
|
|
||||||
local player
|
|
||||||
|
|
||||||
if (id == nil) then
|
|
||||||
return nil
|
|
||||||
--error("You must have an ID to search")
|
|
||||||
end
|
|
||||||
|
|
||||||
for i,v in ipairs(itemList) do
|
|
||||||
if (v.playerID == id) then
|
|
||||||
player = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return player
|
|
||||||
end
|
|
||||||
|
|
||||||
function PlayerManager:playerExist(id)
|
|
||||||
return (self.players[id] ~= nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
function PlayerManager:playerHaveObject(id)
|
|
||||||
player = self:getPlayerByID(id)
|
|
||||||
|
|
||||||
if (player == nil) then
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- UPDATE FUNCTIONS
|
|
||||||
-- Update the death timer and respawn the player when it's done
|
|
||||||
|
|
||||||
function PlayerManager:update(dt)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
-- DRAW FUNCTIONS
|
|
||||||
-- Functions made to draw the HUD
|
|
||||||
|
|
||||||
function PlayerManager:drawHUD(dt)
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return PlayerManager
|
|
|
@ -1,10 +1,6 @@
|
||||||
local World2D = require "core.modules.world.world2D"
|
local World2D = require "core.modules.world.world2D"
|
||||||
local World = World2D:extend()
|
local World = World2D:extend()
|
||||||
|
|
||||||
local Obj = require "scenes.levels.entities"
|
|
||||||
|
|
||||||
local Sti = require "libs.sti"
|
|
||||||
|
|
||||||
-- INIT FUNCTIONS
|
-- INIT FUNCTIONS
|
||||||
-- All functions to init the world and the map
|
-- All functions to init the world and the map
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue