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 Inventory = require "game.inventory"
|
||||
|
||||
local binser = require "libs.binser"
|
||||
local binser = require "core.libs.binser"
|
||||
|
||||
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 World = World2D:extend()
|
||||
|
||||
local Obj = require "scenes.levels.entities"
|
||||
|
||||
local Sti = require "libs.sti"
|
||||
|
||||
-- INIT FUNCTIONS
|
||||
-- All functions to init the world and the map
|
||||
|
||||
|
|
Loading…
Reference in New Issue