chore: remove most unused stuff from older versions

This commit is contained in:
Kazhnuz 2019-06-17 16:29:57 +02:00
parent b3882b388f
commit af7cc04da9
32 changed files with 1 additions and 7233 deletions

View File

@ -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"

View File

@ -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

View File

@ -1,9 +0,0 @@
local AssetManager = Object:extend()
local SoundManager = "libs.assets.sfx"
function AssetManager:new()
end
return AssetManager

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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})

View File

@ -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

View File

@ -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})

View File

@ -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})

View File

@ -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,
}

View File

@ -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})

View File

@ -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 &#9679;");
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 &#9675;");
/* 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 = {
["<"] = "&lt;",
["&"] = "&amp;",
['"'] = "&quot;",
["'"] = "&#039;",
}
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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