diff --git a/imperium-porcorum.love/core/callbacks.lua b/imperium-porcorum.love/core/callbacks.lua
new file mode 100644
index 0000000..39fe422
--- /dev/null
+++ b/imperium-porcorum.love/core/callbacks.lua
@@ -0,0 +1,46 @@
+-- callbacks.lua :: load the callbacks from love2D
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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.
+]]
+
+function love.update(dt)
+ core:update(dt)
+end
+
+function love.draw()
+ core:draw()
+end
+
+function love.mousemoved(x, y, dx, dy)
+ core:mousemoved(x, y, dx, dy)
+end
+
+function love.mousepressed( x, y, button, istouch )
+ core:mousepressed(x, y, button, istouch)
+end
+
+function love.keypressed( key, scancode, isrepeat )
+ core:keypressed( key, scancode, isrepeat )
+end
+
+function love.keyreleased(key)
+ core:keyreleased( key )
+end
diff --git a/imperium-porcorum.love/core/debug.lua b/imperium-porcorum.love/core/debug.lua
index 9a85811..fe6076d 100644
--- a/imperium-porcorum.love/core/debug.lua
+++ b/imperium-porcorum.love/core/debug.lua
@@ -23,7 +23,8 @@
local DebugSystem = Object:extend()
-local lovebird = require("libs.lovebird")
+local cwd = (...):gsub('%.debug$', '') .. "."
+local lovebird = require(cwd .. "libs.lovebird")
function DebugSystem:new(controller, active)
self.controller = controller
diff --git a/imperium-porcorum.love/core/init.lua b/imperium-porcorum.love/core/init.lua
index 63bba08..1831d01 100644
--- a/imperium-porcorum.love/core/init.lua
+++ b/imperium-porcorum.love/core/init.lua
@@ -23,15 +23,29 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
+local cwd = (...):gsub('%.init$', '') .. "."
+
+-- GLOBAL UTILS/FUNCTION LOADING
+-- Load in the global namespace utilities that'll need to be reusable everywhere
+-- in the game
+
+Object = require(cwd .. "libs.classic")
+utils = require(cwd .. "utils")
+
local CoreSystem = Object:extend()
-local DebugSystem = require "core.debug"
+local DebugSystem = require(cwd .. "debug")
-local Options = require "core.options"
-local Input = require "core.input"
-local Screen = require "core.screen"
-local Lang = require "core.lang"
-local SceneManager= require "core.scenemanager"
+local Options = require(cwd .. "options")
+local Input = require(cwd .. "input")
+local Screen = require(cwd .. "screen")
+local Lang = require(cwd .. "lang")
+local SceneManager = require(cwd .. "scenemanager")
+
+require(cwd .. "callbacks")
+
+-- INIT FUNCTIONS
+-- Initialize and configure the core object
function CoreSystem:new()
self.debug = DebugSystem(self)
@@ -39,8 +53,12 @@ function CoreSystem:new()
self.input = Input(self)
self.screen = Screen(self)
self.scenemanager = SceneManager(self)
+ self.lang = Lang(self)
end
+-- MOUSE FUNCTIONS
+-- get directly the mouse when needed
+
function CoreSystem:mousemoved(x, y, dx, dy)
local x, y = self.screen:project(x, y)
local dx, dy = self.screen:project(dx, dy)
@@ -52,6 +70,20 @@ function CoreSystem:mousepressed( x, y, button, istouch )
self.scenemanager:mousepressed( x, y, button, istouch )
end
+-- KEYBOARD FUNCTIONS
+-- get directly the keyboard when needed
+
+function CoreSystem:keypressed( key, scancode, isrepeat )
+ self.scenemanager:keypressed( key, scancode, isrepeat )
+end
+
+function CoreSystem:keyreleased( key )
+ self.scenemanager:keyreleased( key )
+end
+
+-- UPDATE FUNCTIONS
+-- Load every sytem every update functions of the scene and objects
+
function CoreSystem:update(dt)
self.debug:update(dt)
self.input:update(dt)
@@ -59,10 +91,16 @@ function CoreSystem:update(dt)
self.scenemanager:update(dt)
end
+-- DRAW FUNCTIONS
+-- Draw the whole game
+
function CoreSystem:draw()
self.scenemanager:draw()
end
+-- EXIT FUNCTIONS
+-- Quit the game
+
function CoreSystem:exit()
self.options:save()
love.event.quit()
diff --git a/imperium-porcorum.love/core/input.lua b/imperium-porcorum.love/core/input.lua
index 7513d25..539e8f8 100644
--- a/imperium-porcorum.love/core/input.lua
+++ b/imperium-porcorum.love/core/input.lua
@@ -24,21 +24,32 @@
local InputManager = Object:extend()
+-- INIT FUNCTIONS
+-- Initialize and configure the controller system
+
function InputManager:new(controller)
self.controller = controller
- self.data = self.controller.options.data.input[1]
+ self.data = self.controller.options:getInputData()
- self.keys = self:getKeyList()
- self.fakekeys = self:getKeyList()
+ self:initKeys()
end
-function InputManager:isDown(padkey)
+function InputManager:initKeys()
+ self.fakekeys = self:getKeyList(1)
+
+ self.sources = self:getSources()
+ self.fakesources = self:getSources()
+end
+
+-- INFO FUNCTIONS
+-- Get functions from the controller object
+
+function InputManager:isDown(sourceid, padkey)
local isdown = false
- if self.data.type == "keyboard" then
- local key = self.data.keys[padkey]
+
+ if self.data[sourceid].type == "keyboard" then
+ local key = self.data[sourceid].keys[padkey]
isdown = love.keyboard.isDown(key)
- if isdown then
- end
else
print("Warning: unsupported input device")
end
@@ -46,72 +57,93 @@ function InputManager:isDown(padkey)
return isdown
end
-function InputManager:getKeyList()
+function InputManager:getSources()
+ local sources = {}
+ for i,v in ipairs(self.data) do
+ sources[i] = {}
+ sources[i].keys = self:getKeyList(i)
+ end
+
+ return sources
+end
+
+function InputManager:getKeyList(sourceid)
local keys = {}
- for k,v in pairs(self.data.keys) do
- keys[k] = {}
- keys[k].isDown = false
- keys[k].isPressed = false
- keys[k].isReleased = false
- keys[k].test = "ok"
+ if self.data[sourceid] ~= nil then
+ for k,v in pairs(self.data[sourceid].keys) do
+ keys[k] = {}
+ keys[k].isDown = false
+ keys[k].isPressed = false
+ keys[k].isReleased = false
+ keys[k].test = "ok"
+ end
end
return keys
end
-function InputManager:translateAction(key)
- --TODO:depreciated function
- local padkey = ""
- for k,v in pairs(self.data.keys) do
- if v == key then padkey = k end
- end
- return padkey
-end
-
-function InputManager:getKey(padkey)
+function InputManager:getKey(sourceid, padkey)
local padkey = padkey
- for k,v in pairs(self.data.keys) do
+ for k,v in pairs(self.data[sourceid].keys) do
if (k == padkey) then key = v end
end
return key
end
+-- KEY MANAGEMENT FUNCTIONS
+-- Manage pressed keys
+
function InputManager:flushKeys()
- self.keys = {}
- for k,v in pairs(self.data.keys) do
- self.keys[k] = {}
- self.keys[k].isDown = false
- self.keys[k].isPressed = false
- self.keys[k].isReleased = false
- self.keys[k].test = "ok"
+ for i,v in ipairs(self.sources) do
+ self:flushSourceKeys(i)
end
end
-function InputManager:update(dt)
- for k,v in pairs(self.keys) do
- local isDown = self:isDown(k)
+function InputManager:flushSourceKeys(sourceid)
+ self.keys = {}
+ for k,v in pairs(self.sources[sourceid].keys) do
+ v = {}
+ v.isDown = false
+ v.isPressed = false
+ v.isReleased = false
+ end
+end
+
+
+function InputManager:checkKeys(sourceid)
+ for k,v in pairs(self.sources[sourceid].keys) do
+ local isDown = self:isDown(sourceid, k)
if (isDown) then
- if not (self.keys[k].isDown) then
- self.keys[k].isDown = true
- self.keys[k].isPressed = true
- self.keys[k].isReleased = false
+ if not (self.sources[sourceid].keys[k].isDown) then
+ self.sources[sourceid].keys[k].isDown = true
+ self.sources[sourceid].keys[k].isPressed = true
+ self.sources[sourceid].keys[k].isReleased = false
else
- if (self.keys[k].isPressed) then
- self.keys[k].isPressed = false
+ if (self.sources[sourceid].keys[k].isPressed) then
+ self.sources[sourceid].keys[k].isPressed = false
end
end
else
- if (self.keys[k].isDown) then
- self.keys[k].isDown = false
- self.keys[k].isPressed = false
- self.keys[k].isReleased = true
+ if (self.sources[sourceid].keys[k].isDown) then
+ self.sources[sourceid].keys[k].isDown = false
+ self.sources[sourceid].keys[k].isPressed = false
+ self.sources[sourceid].keys[k].isReleased = true
else
- if (self.keys[k].isReleased) then
- self.keys[k].isReleased = false
+ if (self.sources[sourceid].keys[k].isReleased) then
+ self.sources[sourceid].keys[k].isReleased = false
end
end
end
end
end
+-- UPDATE FUNCTIONS
+-- Check every step pressed keys
+
+function InputManager:update(dt)
+ for i,v in ipairs(self.sources) do
+ self:checkKeys(i)
+ end
+end
+
return InputManager
diff --git a/imperium-porcorum.love/core/lang.lua b/imperium-porcorum.love/core/lang.lua
index 1144806..3691707 100644
--- a/imperium-porcorum.love/core/lang.lua
+++ b/imperium-porcorum.love/core/lang.lua
@@ -23,30 +23,107 @@
]]
local LanguageManager = Object:extend()
-local langs = require "datas.languages"
+
+local TRANSLATION_PATH = "datas/languages/"
+
+-- INIT FUNCTIONS
+-- Initialize and configure the translation system
function LanguageManager:new(controller)
self.controller = controller
- self:setLang(self.controller.options.data.language)
-end
-function LanguageManager:getStringList(library, file)
- return require(self.lang .. "." .. library .. "." .. file)
-end
-
-function LanguageManager:getLangName(lang)
- local langnames = langs.available_langs
- return langnames[lang]
-end
-
-function LanguageManager:getCurrentLangName()
- local langnames = langs.available_langs
- return langnames[self.lang]
+ self.data = self:getTranslationData()
+ self:setLang(self.controller.options.data.language.current)
end
function LanguageManager:setLang(lang)
- self.controller.options.data.language = lang
- self.lang = self.controller.options.data.language
+ self.controller.options.data.language.current = lang
+ self.lang = self.controller.options.data.language.current
+end
+
+-- INFO FUNCTIONS
+-- Get informations from the translation manager
+
+function LanguageManager:getCurrentLang()
+ return self.data.language.current
+end
+
+function LanguageManager:getDefaultLang()
+ return self.data.language.default
+end
+
+function LanguageManager:getTranslationData()
+ return self.controller.options.data.language
+end
+
+function LanguageManager:getLangMetadata(lang)
+ local langfilepath = self.data.path .. lang
+
+ return require(langfilepath)
+end
+
+function LanguageManager:getLangName(lang)
+ local metadata = self:getLangMetadata(lang)
+
+ return metadata.name
+end
+
+function LanguageManager:getCurrentLangName()
+ return self:getLangName(self.data.current)
+end
+
+function LanguageManager:isLangAvailable(lang)
+ local isAvailable = false
+
+ for i,v in ipairs(self.data.available) do
+ if v == lang then
+ isAvailable = true
+ end
+ end
+
+ return isAvailable
+end
+
+-- TRANSLATION FUNCTIONS
+-- get the translation of a string
+
+function LanguageManager:getTranslationStringList(lang, library)
+ local _path = self.data.path .. lang .. "/" .. library
+ local fileinfo = love.filesystem.getInfo(_path .. ".lua")
+ local list = nil
+
+ if fileinfo ~= nil then
+ list = require(_path)
+ else
+ print("WARNING: file " .. _path .. " do not exists")
+ end
+
+ return list
+end
+
+function LanguageManager:translateFromLang(lang, library, stringToTranslate)
+ local _stringlist = self:getTranslationStringList(lang, library)
+
+ if _stringlist == nil then
+ return nil
+ else
+ return _stringlist[stringToTranslate]
+ end
+end
+
+function LanguageManager:translate(library, string)
+ local translation = self:translateFromLang(self.data.current, library, string)
+
+ if (translation == nil) then
+ translation = self:translateFromLang(self.data.default, library, string)
+ end
+
+ if (translation == nil) then
+ translation = string
+ print("WARNING: no translation path found for " .. string .. " in " .. library)
+ end
+
+ return translation
end
return LanguageManager
diff --git a/imperium-porcorum.love/core/libs/binser.lua b/imperium-porcorum.love/core/libs/binser.lua
new file mode 100644
index 0000000..5aa1299
--- /dev/null
+++ b/imperium-porcorum.love/core/libs/binser.lua
@@ -0,0 +1,687 @@
+-- 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
+}
diff --git a/imperium-porcorum.love/core/libs/classic.lua b/imperium-porcorum.love/core/libs/classic.lua
new file mode 100644
index 0000000..cbd6f81
--- /dev/null
+++ b/imperium-porcorum.love/core/libs/classic.lua
@@ -0,0 +1,68 @@
+--
+-- classic
+--
+-- Copyright (c) 2014, rxi
+--
+-- This module is free software; you can redistribute it and/or modify it under
+-- the terms of the MIT license. See LICENSE for details.
+--
+
+
+local Object = {}
+Object.__index = Object
+
+
+function Object:new()
+end
+
+
+function Object:extend()
+ local cls = {}
+ for k, v in pairs(self) do
+ if k:find("__") == 1 then
+ cls[k] = v
+ end
+ end
+ cls.__index = cls
+ cls.super = self
+ setmetatable(cls, self)
+ return cls
+end
+
+
+function Object:implement(...)
+ for _, cls in pairs({...}) do
+ for k, v in pairs(cls) do
+ if self[k] == nil and type(v) == "function" then
+ self[k] = v
+ end
+ end
+ end
+end
+
+
+function Object:is(T)
+ local mt = getmetatable(self)
+ while mt do
+ if mt == T then
+ return true
+ end
+ mt = getmetatable(mt)
+ end
+ return false
+end
+
+
+function Object:__tostring()
+ return "Object"
+end
+
+
+function Object:__call(...)
+ local obj = setmetatable({}, self)
+ obj:new(...)
+ return obj
+end
+
+
+return Object
diff --git a/imperium-porcorum.love/core/libs/cscreen.lua b/imperium-porcorum.love/core/libs/cscreen.lua
new file mode 100644
index 0000000..579ea0d
--- /dev/null
+++ b/imperium-porcorum.love/core/libs/cscreen.lua
@@ -0,0 +1,99 @@
+--[[
+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
diff --git a/imperium-porcorum.love/core/libs/lovebird.lua b/imperium-porcorum.love/core/libs/lovebird.lua
new file mode 100644
index 0000000..8b296eb
--- /dev/null
+++ b/imperium-porcorum.love/core/libs/lovebird.lua
@@ -0,0 +1,737 @@
+--
+-- 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"] = [[
+
+
+
+
+
+
+
+ lovebird
+
+
+
+
+
+
+
+
+]]
+
+
+lovebird.pages["buffer"] = [[ ]]
+
+
+lovebird.pages["env.json"] = [[
+
+{
+ "valid": true,
+ "path": "",
+ "vars": [
+
+ {
+ "key": "",
+ "value": ,
+ "type": "",
+ },
+
+ ]
+}
+]]
+
+
+
+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", f)
+ str = "local echo " .. params .. " = ..." .. str
+ local fn = assert(lovebird.loadstring(str, chunkname))
+ return function(...)
+ local output = {}
+ local echo = function(str) table.insert(output, str) end
+ fn(echo, ...)
+ return table.concat(lovebird.map(output, tostring))
+ end
+end
+
+
+function lovebird.map(t, fn)
+ local res = {}
+ for k, v in pairs(t) do res[k] = fn(v) end
+ return res
+end
+
+
+function lovebird.trace(...)
+ local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
+ print(str)
+ if not lovebird.wrapprint then lovebird.print(str) end
+end
+
+
+function lovebird.unescape(str)
+ local f = function(x) return string.char(tonumber("0x"..x)) end
+ return (str:gsub("%+", " "):gsub("%%(..)", f))
+end
+
+
+function lovebird.parseurl(url)
+ local res = {}
+ res.path, res.search = url:match("/([^%?]*)%??(.*)")
+ res.query = {}
+ for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
+ res.query[k] = lovebird.unescape(v)
+ end
+ return res
+end
+
+
+local htmlescapemap = {
+ ["<"] = "<",
+ ["&"] = "&",
+ ['"'] = """,
+ ["'"] = "'",
+}
+
+function lovebird.htmlescape(str)
+ return ( str:gsub("[<&\"']", htmlescapemap) )
+end
+
+
+function lovebird.truncate(str, len)
+ if #str <= len then
+ return str
+ end
+ return str:sub(1, len - 3) .. "..."
+end
+
+
+function lovebird.compare(a, b)
+ local na, nb = tonumber(a), tonumber(b)
+ if na then
+ if nb then return na < nb end
+ return false
+ elseif nb then
+ return true
+ end
+ return tostring(a) < tostring(b)
+end
+
+
+function lovebird.checkwhitelist(addr)
+ if lovebird.whitelist == nil then return true end
+ for _, a in pairs(lovebird.whitelist) do
+ local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
+ if addr:match(ptn) then return true end
+ end
+ return false
+end
+
+
+function lovebird.clear()
+ lovebird.lines = {}
+ lovebird.buffer = ""
+end
+
+
+function lovebird.pushline(line)
+ line.time = os.time()
+ line.count = 1
+ table.insert(lovebird.lines, line)
+ if #lovebird.lines > lovebird.maxlines then
+ table.remove(lovebird.lines, 1)
+ end
+ lovebird.recalcbuffer()
+end
+
+
+function lovebird.recalcbuffer()
+ local function doline(line)
+ local str = line.str
+ if not lovebird.allowhtml then
+ str = lovebird.htmlescape(line.str):gsub("\n", "
")
+ end
+ if line.type == "input" then
+ str = '' .. str .. ''
+ else
+ if line.type == "error" then
+ str = '! ' .. str
+ str = '' .. str .. ''
+ end
+ if line.count > 1 then
+ str = '' .. line.count .. ' ' .. str
+ end
+ if lovebird.timestamp then
+ str = os.date('%H:%M:%S ', line.time) ..
+ str
+ end
+ end
+ return str
+ end
+ lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "
")
+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
diff --git a/imperium-porcorum.love/core/modules/assets/animator.lua b/imperium-porcorum.love/core/modules/assets/animator.lua
index 7bce90c..1498153 100644
--- a/imperium-porcorum.love/core/modules/assets/animator.lua
+++ b/imperium-porcorum.love/core/modules/assets/animator.lua
@@ -24,6 +24,9 @@
local Animator = Object:extend()
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
+
function Animator:new(sprite)
self.sprite = sprite
self.frame = 1
@@ -40,6 +43,9 @@ function Animator:setCustomSpeed(customSpeed)
self.customSpeed = customSpeed or 0
end
+-- UPDATE FUNCTIONS
+-- Update the animation of the animator
+
function Animator:update(dt)
if (self.currentAnimation == "") then
print("warning: no current animation data")
@@ -61,21 +67,8 @@ function Animator:update(dt)
end
end
-function Animator:getAnimationDuration(animation)
- return (self.animationData.endAt - self.animationData.startAt) / self.animationData.speed
-end
-
-function Animator:getFrame()
- return self.frame
-end
-
-function Animator:animationExist(name)
- return (self.sprite.data.animations[self.currentAnimation] ~= nil)
-end
-
-function Animator:draw(x, y, r, sx, sy, ox, oy, kx, ky)
- self.sprite:drawFrame(self.frame, x, y, r, sx, sy, ox, oy, kx, ky)
-end
+-- ANIMATION HANDLING FUNCTIONS
+-- Change the animation of the animator
function Animator:changeAnimation(name, restart)
-- Force restart if animation name is different
@@ -98,4 +91,34 @@ function Animator:changeToDefaultAnimation(restart)
self:changeAnimation(self.sprite.data.metadata.defaultAnim, restart)
end
+-- INFO FUNCTIONS
+-- get information with these functions
+
+function Animator:getAnimationDuration(animation)
+ return (self.animationData.endAt - self.animationData.startAt) / self.animationData.speed
+end
+
+function Animator:getFrame()
+ return self.frame
+end
+
+function Animator:animationExist(name)
+ return (self.sprite.data.animations[self.currentAnimation] ~= nil)
+end
+
+function Animator:getDimensions()
+ return self.sprite:getDimensions()
+end
+
+-- DRAW FUNCTIONS
+-- Draw animations using these functions
+
+function Animator:draw(x, y, r, sx, sy, ox, oy, kx, ky)
+ self.sprite:drawFrame(self.frame, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Animator:drawMask(x, y, r, sx, sy, ox, oy, kx, ky)
+ self.sprite:drawFrameMask(self.frame, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
return Animator
diff --git a/imperium-porcorum.love/core/modules/assets/autotile.lua b/imperium-porcorum.love/core/modules/assets/autotile.lua
index 4104303..b441ab1 100644
--- a/imperium-porcorum.love/core/modules/assets/autotile.lua
+++ b/imperium-porcorum.love/core/modules/assets/autotile.lua
@@ -23,9 +23,13 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
-local Tileset = require "core.modules.assets.tileset"
+local cwd = (...):gsub('%.autotile$', '') .. "."
+local Tileset = require(cwd .. "tileset")
local Autotile = Object:extend()
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
+
function Autotile:new(filepath)
self.tileset = Tileset(filepath)
@@ -35,6 +39,9 @@ function Autotile:new(filepath)
self.tilesize = self.metadata.width
end
+-- DRAW FUNCTIONS
+-- Draw tileset using these functions
+
function Autotile:drawtile(i, j, x, y, r, sx, sy, ox, oy, kx, ky)
local i = i or 1
local j = j or 1
diff --git a/imperium-porcorum.love/core/modules/assets/background.lua b/imperium-porcorum.love/core/modules/assets/background.lua
index 862050b..7a0ac56 100644
--- a/imperium-porcorum.love/core/modules/assets/background.lua
+++ b/imperium-porcorum.love/core/modules/assets/background.lua
@@ -24,6 +24,9 @@
local Background = Object:extend()
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
+
function Background:new(filepath)
self.image = love.graphics.newImage(filepath)
self.batch = love.graphics.newSpriteBatch(self.image , 1000 )
diff --git a/imperium-porcorum.love/core/modules/assets/fonts.lua b/imperium-porcorum.love/core/modules/assets/fonts.lua
index f2bc7c9..c527144 100644
--- a/imperium-porcorum.love/core/modules/assets/fonts.lua
+++ b/imperium-porcorum.love/core/modules/assets/fonts.lua
@@ -25,6 +25,7 @@
local Font = Object:extend()
+-- INIT FUNCTIONS
-- Initilizing and configuring option
function Font:new(filename, size)
@@ -70,7 +71,8 @@ function Font:setLineHeight(height)
self.font:setLineHeight(height)
end
--- get information functions
+-- INFO FUNCTIONS
+-- get information with these functions
function Font:getHeight()
local font = self.font
@@ -91,7 +93,8 @@ function Font:getColor()
return self.color
end
--- print functions
+-- DRAW FUNCTIONS
+-- print text using theses functions
function Font:draw(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
-- draw text with color and effect applied
@@ -124,6 +127,7 @@ function Font:printf(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
end
-- FILTER SYSTEM
+-- With these filter, you can apply custom effects to the fonts
function Font:applyFilter(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
if self.filter == "shadow" then
diff --git a/imperium-porcorum.love/core/modules/assets/imagefonts.lua b/imperium-porcorum.love/core/modules/assets/imagefonts.lua
index 16e0b7a..86f318b 100644
--- a/imperium-porcorum.love/core/modules/assets/imagefonts.lua
+++ b/imperium-porcorum.love/core/modules/assets/imagefonts.lua
@@ -1,6 +1,35 @@
-local Font = require "core.modules.assets.fonts"
+-- assets/fonts :: the imagefonts object, which are basically a bitmap version
+-- of the font object.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.imagefonts$', '') .. "."
+
+local Font = require(cwd.. "fonts")
local ImageFont = Font:extend()
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
+
function ImageFont:new(filename, extraspacing)
local data = require(filename)
local extraspacing = extraspacing or data.extraspacing or 1
diff --git a/imperium-porcorum.love/core/modules/assets/init.lua b/imperium-porcorum.love/core/modules/assets/init.lua
index b6e11dc..1634110 100644
--- a/imperium-porcorum.love/core/modules/assets/init.lua
+++ b/imperium-porcorum.love/core/modules/assets/init.lua
@@ -24,51 +24,38 @@
local Assets = Object:extend()
-local Sprite = require "core.modules.assets.sprites"
-local Font = require "core.modules.assets.fonts"
-local ImageFont = require "core.modules.assets.imagefonts"
+local cwd = (...):gsub('%.init$', '') .. "."
-local Tileset = require "core.modules.assets.tileset"
-local Autotile = require "core.modules.assets.autotile"
-local Background = require "core.modules.assets.background"
+local Texture = require(cwd .. "texture")
+local Sprite = require(cwd .. "sprites")
+local Font = require(cwd .. "fonts")
+local ImageFont = require(cwd .. "imagefonts")
+
+local Tileset = require(cwd .. "tileset")
+local Autotile = require(cwd .. "autotile")
+local Background = require(cwd .. "background")
+
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
function Assets:new()
- self.sprites = {}
- self.sfx = {}
- self.fonts = {}
- self.music = nil
- self:clearBackgrounds()
- self:clearFonts()
- self:clearAutotile()
- self:clearTileset()
-
- self.images = {}
+ self:clear()
self.isActive = true
end
-function Assets:init()
- self.sprites = {}
- self.sfx = {}
- self.fonts = {}
- self.music = nil
- self.backgrounds= {}
- self:clearFonts()
-
- self.images = {}
-end
-
function Assets:clear()
-- TODO: destroy individually each texture/image when assets are cleared
- self.sprites = {}
- self.sfx = {}
- self.fonts = {}
- self.music = nil
- self.backgrounds= {}
+ self:clearSprites()
+ self:clearSFX()
self:clearFonts()
+ self:resetMusic()
+ self:clearBackgrounds()
+ self:clearFonts()
+ self:clearTileset()
- self.images = {}
+ self:clearImages()
end
function Assets:update(dt)
@@ -77,7 +64,87 @@ function Assets:update(dt)
end
end
--- SFX et Musique
+-- IMPORT FUNCTIONS
+-- Easilly import assets
+
+function Assets:batchImport(datafile)
+ local datas = require(datafile)
+
+ for asset_type, assets in pairs(datas) do
+ if (asset_type == "autotiles") then
+ self:importAutotiles(assets)
+ elseif (asset_type == "backgrounds") then
+ self:importBackgrounds(assets)
+ elseif (asset_type == "fonts") then
+ self:importFonts(assets)
+ elseif (asset_type == "imagefonts") then
+ self:importImageFonts(assets)
+ elseif (asset_type == "images") then
+ self:importTextures(assets)
+ elseif (asset_type == "sprites") then
+ self:importSprites(assets)
+ elseif (asset_type == "textures") then
+ self:importTextures(assets)
+ elseif (asset_type == "tilesets") then
+ self:importTilesets(assets)
+ elseif (asset_type == "sfx") then
+ self:importSFX(assets)
+ else
+ print("Unkown asset type : " .. asset_type)
+ end
+ end
+end
+
+function Assets:importAutotiles(assets)
+ for i, asset in ipairs(assets) do
+ self:addAutotile(asset[1], asset[2])
+ end
+end
+
+function Assets:importBackgrounds(assets)
+ for i, asset in ipairs(assets) do
+ self:addBackground(asset[1], asset[2])
+ end
+end
+
+function Assets:importFonts(assets)
+ for i, asset in ipairs(assets) do
+ self:addFont(asset[1], asset[2], asset[3])
+ end
+end
+
+function Assets:importImageFonts(assets)
+ for i, asset in ipairs(assets) do
+ self:addImageFont(asset[1], asset[2], asset[3])
+ end
+end
+
+function Assets:importSprites(assets)
+ for i, asset in ipairs(assets) do
+ self:addSprite(asset[1], asset[2])
+ end
+end
+
+function Assets:importTextures(assets)
+ for i, asset in ipairs(assets) do
+ self:addImage(asset[1], asset[2])
+ end
+end
+
+function Assets:importTilesets(assets)
+ for i, asset in ipairs(assets) do
+ self:addTileset(asset[1], asset[2])
+ end
+end
+
+function Assets:importSFX(assets)
+ for i, asset in ipairs(assets) do
+ self:addSFX(asset[1], asset[2])
+ end
+end
+
+-- SFX & MUSICS
+-- Handle sound effects and musics
function Assets:addSFX(name, filepath)
self:newSFX(name, filepath)
@@ -92,14 +159,6 @@ function Assets:clearSFX()
self.sfx = {}
end
-function Assets:setMusic(filename)
- if filename ~= nil then
- love.audio.stop( )
- self.music = love.audio.newSource(filename, "stream" )
- self.music:setVolume(core.options.data.audio.music / 100)
- end
-end
-
function Assets:playSFX(filename)
if not (self.sfx[filename] == nil) then
self.sfx[filename]:stop()
@@ -108,9 +167,11 @@ function Assets:playSFX(filename)
end
end
-function Assets:playMusic()
- if not (self.music == nil) then
- love.audio.play(self.music)
+function Assets:setMusic(filename)
+ if filename ~= nil then
+ love.audio.stop( )
+ self.music = love.audio.newSource(filename, "stream" )
+ self.music:setVolume(core.options.data.audio.music / 100)
end
end
@@ -118,49 +179,63 @@ function Assets:silence()
love.audio.stop()
end
--- Background --
+function Assets:resetMusic()
+ self.music = nil
+end
+
+function Assets:playMusic()
+ if not (self.music == nil) then
+ love.audio.play(self.music)
+ end
+end
+
+-- IMAGES FUNCTIONS
+-- Create directly texture items
function Assets:addImage(name, filename)
- self.images[name] = love.graphics.newImage(filename)
+ self.images[name] = Texture(filename)
end
function Assets:drawImage(name, x, y, r, sx, sy, ox, oy, kx, ky)
- love.graphics.draw(self.images[name], x, y, r, sx, sy, ox, oy, kx, ky)
+ self.images[name]:draw(x, y, r, sx, sy, ox, oy, kx, ky)
end
--- Images --
+function Assets:clearImages()
+ self.images = {}
+end
+
+-- BACKGROUNDS FUNCTIONS
+-- Automatic tiling texture
+
+function Assets:addBackground(name, filepath)
+ -- TODO: rework entirely background to work at any size
+ self.backgrounds[name] = Background(filepath)
+end
function Assets:clearBackgrounds()
self.backgrounds = {}
end
-function Assets:addBackground(name, filepath)
- self.backgrounds[name] = Background(filepath)
-end
-
--- SPRITES --
-
+-- SPRITES FUNCTIONS
+-- Animated tileset
function Assets:addSprite(name, filepath)
self.sprites[name] = Sprite(filepath)
end
-function Assets:clearSprites()
- self.sprites = {}
-end
-
function Assets:animationsUpdate(dt)
for i,v in pairs(self.sprites) do
v:update(dt)
end
end
--- FONTS --
-
-function Assets:clearFonts()
- self.fonts = {}
+function Assets:clearSprites()
+ self.sprites = {}
end
+-- FONTS FUNCTIONS
+-- Handles fonts and imagesfonts
+
function Assets:addFont(key, filename, size)
local font = Font(filename, size)
self.fonts[key] = font
@@ -175,7 +250,12 @@ function Assets:getFont(filename)
return self.fonts[filename]
end
--- Tileset
+function Assets:clearFonts()
+ self.fonts = {}
+end
+
+-- TILESET FUNCTIONS
+-- Automatically create quads for a texture
function Assets:addTileset(name, filepath)
self.tileset[name] = Tileset(filepath)
@@ -185,7 +265,8 @@ function Assets:clearTileset()
self.tileset = {}
end
--- Autotile
+-- AUTOTILE FUNCTIONS
+-- Automatically draw tiles
function Assets:addAutotile(name, tilesize)
self.autotile[name] = Autotile(name, tilesize)
@@ -195,4 +276,19 @@ function Assets:clearAutotile()
self.autotile = {}
end
+-- ACTIVITY FUNCTIONS
+-- Handle activity
+
+function Assets:setActivity(activity)
+ self.isActive = activity
+end
+
+function Assets:switchActivity()
+ self.isActive = (self.isActive == false)
+end
+
+function Assets:getActivity()
+ return self.isActive
+end
+
return Assets
diff --git a/imperium-porcorum.love/core/modules/assets/sprites.lua b/imperium-porcorum.love/core/modules/assets/sprites.lua
index 326db34..ddf4f89 100644
--- a/imperium-porcorum.love/core/modules/assets/sprites.lua
+++ b/imperium-porcorum.love/core/modules/assets/sprites.lua
@@ -24,8 +24,13 @@
]]
local Sprite = Object:extend()
-local Animator = require("core.modules.assets.animator")
-local Tileset = require("core.modules.assets.tileset")
+local cwd = (...):gsub('%.sprites$', '') .. "."
+
+local Animator = require(cwd .. "animator")
+local Tileset = require(cwd .. "tileset")
+
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
function Sprite:new(filepath)
self.tileset = Tileset(filepath)
@@ -58,18 +63,36 @@ function Sprite:changeAnimation(name, restart)
self.animator:changeAnimation(name, restart)
end
+-- INFO FUNCTIONS
+-- get information with these functions
+
function Sprite:animationExist(name)
return self.animator:animationExist(name)
end
+function Sprite:getDimensions()
+ return self.tileset:getDimensions()
+end
+
+-- DRAW FUNCTIONS
+-- Draw sprites using these functions
+
function Sprite:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
self.animator:draw(x, y, r, sx, sy, ox, oy, kx, ky)
end
+function Sprite:drawAnimationMask(x, y, r, sx, sy, ox, oy, kx, ky)
+ self.animator:drawMask(x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
function Sprite:drawFrame(frame, x, y, r, sx, sy, ox, oy, kx, ky)
self.tileset:drawTile(frame, x, y, r, sx, sy, ox, oy, kx, ky)
end
+function Sprite:drawFrameMask(frame, x, y, r, sx, sy, ox, oy, kx, ky)
+ self.tileset:drawTileMask(frame, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
function Sprite:drawPart(x, y, w, h, r, sx, sy, ox, oy, kx, ky)
local w = math.floor(w)
local h = math.floor(h)
diff --git a/imperium-porcorum.love/core/modules/assets/texture.lua b/imperium-porcorum.love/core/modules/assets/texture.lua
new file mode 100644
index 0000000..30901f3
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/assets/texture.lua
@@ -0,0 +1,74 @@
+-- assets/texture :: the texture object, essentially used to be able to draw easily
+-- the mask of the texture (used for stuff like flashing sprite, etc)
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Texture = Object:extend()
+
+local function getMask(x, y, r, g, b, a)
+ -- template for defining your own pixel mapping function
+ -- perform computations giving the new values for r, g, b and a
+ -- ...
+ return 1, 1, 1, a
+end
+
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
+
+function Texture:new(filename)
+ self.imageData = love.image.newImageData(filename)
+
+ local maskData = self.imageData:clone()
+ maskData:mapPixel( getMask )
+
+ self.image = love.graphics.newImage( self.imageData )
+ self.mask = love.graphics.newImage( maskData )
+end
+
+-- INFO FUNCTIONS
+-- get information with these functions
+
+function Texture:getDimensions()
+ return self.image:getDimensions()
+end
+
+-- DRAW FUNCTIONS
+-- Draw texture using these functions
+
+function Texture:draw(x, y, r, sx, sy, ox, oy, kx, ky)
+ love.graphics.draw(self.image, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Texture:drawQuad(quad, x, y, r, sx, sy, ox, oy, kx, ky)
+ love.graphics.draw(self.image, quad, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Texture:drawMask(x, y, r, sx, sy, ox, oy, kx, ky)
+ love.graphics.draw(self.mask, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Texture:drawMaskQuad(quad, x, y, r, sx, sy, ox, oy, kx, ky)
+ love.graphics.draw(self.mask, quad, x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+return Texture
diff --git a/imperium-porcorum.love/core/modules/assets/tileset.lua b/imperium-porcorum.love/core/modules/assets/tileset.lua
index c86aa44..55d6643 100644
--- a/imperium-porcorum.love/core/modules/assets/tileset.lua
+++ b/imperium-porcorum.love/core/modules/assets/tileset.lua
@@ -27,9 +27,15 @@
]]
local Tileset = Object:extend()
+local cwd = (...):gsub('%.tileset$', '') .. "."
+
+local Texture = require(cwd .. "texture")
+
+-- INIT FUNCTIONS
+-- Initilizing and configuring option
function Tileset:new(filepath)
- self.texture = love.graphics.newImage(filepath .. ".png")
+ self.texture = Texture(filepath .. ".png")
local data = require(filepath)
self.metadata = data.metadata
@@ -63,6 +69,9 @@ function Tileset:createQuads()
end
+-- INFO FUNCTIONS
+-- get information with these functions
+
function Tileset:getTileID_Grid(x, y)
local n = (y - 1) * self.gridWidth + x
@@ -77,13 +86,35 @@ function Tileset:getTile(n)
return self.quads[n]
end
+function Tileset:getDimensions()
+ return self.width, self.height
+end
+
+-- DRAW FUNCTIONS
+-- Draw tileset using these functions
+
function Tileset:drawTile_Grid(i, j, x, y, r, sx, sy, ox, oy, kx, ky)
local tileID = self:getTileID_Grid(i, j)
- love.graphics.draw(self.texture, self.quads[tileID], x, y, r, sx, sy, ox, oy, kx, ky)
+ local ox = ox or self.metadata.ox
+ local oy = oy or self.metadata.oy
+ self.texture:drawQuad(self.quads[tileID], x, y, r, sx, sy, ox, oy, kx, ky)
end
function Tileset:drawTile(id, x, y, r, sx, sy, ox, oy, kx, ky)
- love.graphics.draw(self.texture, self.quads[id], x, y, r, sx, sy, ox, oy, kx, ky)
+ local ox = ox or self.metadata.ox
+ local oy = oy or self.metadata.oy
+ self.texture:drawQuad(self.quads[id], x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Tileset:drawTileMask_Grid(i, j, x, y, r, sx, sy, ox, oy, kx, ky)
+ local tileID = self:getTileID_Grid(i, j)
+ local ox = ox or self.metadata.ox
+ local oy = oy or self.metadata.oy
+ self.texture:drawMaskQuad(self.quads[tileID], x, y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Tileset:drawTileMask(id, x, y, r, sx, sy, ox, oy, kx, ky)
+ self.texture:drawMaskQuad(self.quads[id], x, y, r, sx, sy, ox, oy, kx, ky)
end
return Tileset
diff --git a/imperium-porcorum.love/core/modules/menusystem/flowbox.lua b/imperium-porcorum.love/core/modules/menusystem/flowbox.lua
index a120f80..c3d9426 100644
--- a/imperium-porcorum.love/core/modules/menusystem/flowbox.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/flowbox.lua
@@ -1,10 +1,36 @@
-local cwd = (...):gsub('%.flowbox$', '') .. "."
-local Menu = require(cwd .. "parent")
+-- flowbox :: flexible box menu, that handle in grid the widgets
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.flowbox$', '') .. "."
+
+local Menu = require(cwd .. "parent")
local FlowBox = Menu:extend()
local menuutils = require(cwd .. "widgets.utils")
+-- INIT FUNCTIONS
+-- Initialize and configure the flowbox
+
function FlowBox:new(menusystem, name, x, y, w, h, slots_hor, slots_vert)
self.view = {}
self.view.slotNumber = slots_hor * slots_vert
@@ -19,6 +45,9 @@ function FlowBox:new(menusystem, name, x, y, w, h, slots_hor, slots_vert)
-- soit un multiple du nombre de slot et de leur dimensions
end
+-- UPDATE FUNCTIONS
+-- Update the menu and its view
+
function FlowBox:updateWidgetSize()
self.widget.h = math.floor( self.h / self.view.lineNumber )
self.widget.w = math.floor( self.w / self.view.colNumber )
@@ -47,6 +76,9 @@ function FlowBox:updateView()
self.view.firstSlot = beginline * self.view.colNumber + 1
end
+-- INFO FUNCTIONS
+-- Get informations
+
function FlowBox:getCoord(id_selected)
id_selected = id_selected - 1 -- On simplifie les calcul en prenant 0 comme départ
local line, col
@@ -55,6 +87,9 @@ function FlowBox:getCoord(id_selected)
return col, line
end
+-- CURSOR FUNCTIONS
+-- Handle the cursor in a 2D menu
+
function FlowBox:moveCursor(new_col, new_line)
local col, line = self:getCoord(self.widget.selected)
local lastcol, lastline = self:getCoord(#self.widget.list)
@@ -93,6 +128,9 @@ function FlowBox:moveCursor(new_col, new_line)
self.widget.selected = (new_line * self.view.colNumber) + new_col + 1
end
+-- KEYS FUNCTIONS
+-- Handle the keyboard/controller inputs
+
function FlowBox:keyreleased(key, code)
local col, line = self:getCoord(self.widget.selected)
if key == 'left' then
@@ -113,11 +151,14 @@ function FlowBox:keyreleased(key, code)
if key == "A" then
if (self.widget.selected >= 1 and self.widget.selected <= #self.widget.list) then
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("key")
end
end
end
+-- MOUSE FUNCTIONS
+-- Handle the mouse/touch pointer
+
function FlowBox:mousemoved(x, y)
local col, line = self:getCoord(self.widget.selected)
local begincol, beginline = self:getCoord(self.view.firstSlot)
@@ -145,11 +186,14 @@ function FlowBox:mousepressed(x, y, button, isTouch)
if widget_selected >= 1 and widget_selected <= #self.widget.list then
self.widget.selected = widget_selected
self:getFocus()
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("pointer")
end
end
+-- DRAW FUNCTIONS
+-- Draw the menu and its content
+
function FlowBox:draw()
self:updateView()
local widgety = self.y
diff --git a/imperium-porcorum.love/core/modules/menusystem/grid.lua b/imperium-porcorum.love/core/modules/menusystem/grid.lua
index 7dec4b3..e1fd8ed 100644
--- a/imperium-porcorum.love/core/modules/menusystem/grid.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/grid.lua
@@ -1,10 +1,36 @@
-local cwd = (...):gsub('%.grid$', '') .. "."
-local Menu = require(cwd .. "parent")
+-- grid :: a menu with arbitrary widget placement and size on a grid.
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.grid$', '') .. "."
+
+local Menu = require(cwd .. "parent")
local GridBox = Menu:extend()
local menuutils = require(cwd .. "widgets.utils")
+-- INIT FUNCTIONS
+-- Initialize and configure the menu
+
function GridBox:new(menusystem, name, x, y, w, h, colNumber, lineNumber)
self.view = {}
self.view.slotNumber = colNumber * lineNumber
@@ -40,6 +66,9 @@ function GridBox:updateWidgetSize()
self.widget.w = math.floor( self.w / self.view.colNumber )
end
+-- INFO FUNCTIONS
+-- Get the info of the widgets
+
function GridBox:getWidgetSize(id)
local slot = self:getWidgetSlot(id)
if slot == 0 then
@@ -106,10 +135,16 @@ function GridBox:getWidgetAtPoint(x, y)
return widgetID
end
+-- UPDATE FUNCTIONS
+-- Update the Grid and its view
+
function GridBox:update(dt)
self.view.firstSlot = 1
end
+-- KEYS FUNCTIONS
+-- Handle the keyboard/manette functions
+
function GridBox:keyreleased(key, code)
slotID = self:getWidgetSlot(self.widget.selected)
local col, line = self.cursor.x, self.cursor.y
@@ -130,7 +165,7 @@ function GridBox:keyreleased(key, code)
end
if key == "A" and self.widget.selected <= #self.widget.list then
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("key")
end
end
@@ -176,6 +211,9 @@ function GridBox:moveLine(direction)
end
end
+-- MOUSE FUNCTIONS
+-- Handle the mouse and activate the widgets with it
+
function GridBox:mousemoved(x, y)
local widgetID = self:getWidgetAtPoint(x, y)
@@ -201,11 +239,14 @@ function GridBox:mousepressed(x, y, button, isTouch)
self:getFocus()
if #self.widget.list > 0 and self.widget.selected > 1 and self.widget.selected <= #self.widget.list then
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("pointer")
end
end
end
+-- DRAW FUNCTIONS
+-- Draw the menu and its content
+
function GridBox:draw()
for i,v in ipairs(self.slots) do
diff --git a/imperium-porcorum.love/core/modules/menusystem/hlistbox.lua b/imperium-porcorum.love/core/modules/menusystem/hlistbox.lua
new file mode 100644
index 0000000..067de32
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/menusystem/hlistbox.lua
@@ -0,0 +1,148 @@
+-- hlistbox : add an horizontal list of widgets.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.hlistbox$', '') .. "."
+
+local Menu = require(cwd .. "parent")
+local HListBox = Menu:extend()
+
+local menuutils = require(cwd .. "widgets.utils")
+
+-- INIT FUNCTIONS
+-- Initialize and configure functions.
+
+function HListBox:new(menusystem, name, x, y, w, h, slotNumber)
+ self.view = {}
+ self.view.slotNumber = slotNumber
+ self.view.firstSlot = 1
+ HListBox.super.new(self, menusystem, name, x, y, w, h)
+ self.w = slotNumber * self.widget.w -- On fait en sorte que la hauteur
+ -- soit un multiple du nombre de slot et de leur hauteur
+end
+
+-- UPDATE FUNCTIONS
+-- Update the menu every step.
+
+function HListBox:updateWidgetSize()
+ self.widget.h = self.h
+ self.widget.w = math.floor( self.w / self.view.slotNumber )
+end
+
+function HListBox:update(dt)
+ self:updateView()
+end
+
+function HListBox:updateView()
+ if self.widget.selected < self.view.firstSlot then
+ self.view.firstSlot = self.widget.selected
+ end
+ if self.widget.selected > self.view.firstSlot + self.view.slotNumber - 1 then
+ self.view.firstSlot = self.widget.selected - self.view.slotNumber + 1
+ end
+
+ if self.view.firstSlot < 1 then
+ self.view.firstSlot = 1
+ end
+end
+
+-- KEYBOARD FUNCTIONS
+-- Handle key check.
+
+function HListBox:keyreleased(key, code)
+
+ if key == 'left' then
+ self:moveCursor(self.widget.selected - 1)
+ end
+
+ if key == 'right' then
+ self:moveCursor(self.widget.selected + 1)
+ end
+
+ if key == "A" then
+ if (self.widget.selected >= 1 and self.widget.selected <= #self.widget.list) then
+ self.widget.list[self.widget.selected]:action("key")
+ end
+ end
+
+ if key == "B" then
+ if (self.widget.cancel >= 1 and self.widget.cancel <= #self.widget.list) then
+ self.widget.list[self.widget.cancel]:action("key")
+ end
+ end
+
+end
+
+-- MOUSE FUNCTIONS
+-- Click and stuff like that.
+
+function HListBox:mousemoved(x, y)
+ local widget_selected = self.view.firstSlot + math.floor(x / self.widget.w)
+
+ if widget_selected >= 1 and widget_selected <= #self.widget.list then
+ self.widget.selected = widget_selected
+ self:getFocus()
+ end
+end
+
+function HListBox:mousepressed(x, y, button, isTouch)
+ local widget_selected = self.view.firstSlot + math.floor(x / self.widget.w)
+
+ if widget_selected >= 1 and widget_selected <= #self.widget.list then
+ self.widget.selected = widget_selected
+ self:getFocus()
+ if #self.widget.list > 0 then
+ self.widget.list[self.widget.selected]:action("pointer")
+ end
+ end
+
+end
+
+-- DRAW FUNCTIONS
+-- Draw the menu and its content
+
+function HListBox:draw()
+ self:updateView()
+ local widgetx = self.x
+ for i,v in ipairs(self.widget.list) do
+ if (i >= self.view.firstSlot) and (i < self.view.firstSlot + self.view.slotNumber) then
+ v:draw(widgetx, self.y, self.widget.w, self.h)
+ if self.widget.selected == i and self:haveFocus() == true then
+ v:drawSelected(widgetx, self.y, self.widget.w, self.h)
+ else
+ v:draw(widgetx, self.y, self.widget.w, self.h)
+ end
+ widgetx = widgetx + self.widget.w
+ end
+ end
+end
+
+function HListBox:drawCursor()
+ self:updateView()
+ if (self.widget.selected >= 1 and self.widget.selected <= #self.widget.list) then
+ local w, h = self:getWidgetSize()
+ local x = (self.widget.selected - self.view.firstSlot) * w
+ menuutils.drawCursor(self.x + x,self.y, w, h)
+ end
+end
+
+return HListBox
diff --git a/imperium-porcorum.love/core/modules/menusystem/init.lua b/imperium-porcorum.love/core/modules/menusystem/init.lua
index db5ee4d..780796d 100644
--- a/imperium-porcorum.love/core/modules/menusystem/init.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/init.lua
@@ -1,54 +1,163 @@
-local MenuSystem = Object:extend()
+-- menusystem :: the controller of the menu system. This object handle the
+-- different menu objects
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.init$', '') .. "."
+local MenuSystem = Object:extend()
+
+-- Load the differents menu object to get an easy access
MenuSystem.Parent = require(cwd .. "parent")
MenuSystem.ListBox = require(cwd .. "listbox")
MenuSystem.FlowBox = require(cwd .. "flowbox")
MenuSystem.Grid = require(cwd .. "grid")
-MenuSystem.TextMenu = require(cwd .. "textmenu")
+-- load widgets object
MenuSystem.Widget = require(cwd .. "widgets")
+-- INIT FUNCTIONS
+-- Initialize and configure the menu controller
---local VirtualPad = require "modules.virtualpad"
-
-function MenuSystem:new()
+function MenuSystem:new(scene)
+ self.scene = scene
self.menus = {}
self.focusedMenu = ""
+ self.isActive = true
+ self.lockWorld = false
+ self.lockAssets = false
end
function MenuSystem:reset()
self.menus = {}
end
+-- ACTIVATION FUNCTIONS
+-- Activate and deactivate the whole menusystem
+
+function MenuSystem:activate()
+ self.isActive = true
+
+ if (self.lockWorld) then
+ if (self.scene.world ~= nil) then
+ self.scene.world:setActivity(false)
+ end
+ end
+
+ if (self.lockAssets) then
+ if (self.scene.assets ~= nil) then
+ self.scene.assets:setActivity(false)
+ end
+ end
+end
+
+function MenuSystem:deactivate()
+ self.isActive = false
+
+ if (self.lockWorld) then
+ if (self.scene.world ~= nil) then
+ self.scene.world:setActivity(true)
+ end
+ end
+
+ if (self.lockAssets) then
+ if (self.scene.assets ~= nil) then
+ self.scene.assets:setActivity(true)
+ end
+ end
+end
+
+function MenuSystem:getActiveState()
+ return self.isActive
+end
+
+function MenuSystem:lockWorldWhenActive(state)
+ self.lockWorld = state
+end
+
+function MenuSystem:lockAssetsWhenActive(state)
+ self.lockAssets = state
+end
+
+
+-- MENUS FUNCTIONS
+-- Controle the menus of the menusystem
+
function MenuSystem:addMenu(name, menu)
self.menus[name] = menu
end
-function MenuSystem:update(dt)
- self:removeDestroyedMenus()
- for k,v in pairs(self.menus) do
- v:update(dt)
- v:updateWidgets(dt)
- end
+function MenuSystem:menuExist(name)
+ return (self.menus[name] ~= nil)
+end
- if self.menus[self.focusedMenu] ~= nil then
- -- Only check buttons if the current focused menu is actually active
- if self.menus[self.focusedMenu].isActive then
- for k,v in pairs(self.keys) do
- if self.keys[k].isPressed then
- self.menus[self.focusedMenu]:keyreleased(k)
- end
- end
+function MenuSystem:switchMenu(menu)
+ for k,v in pairs(self.menus) do
+ if k == menu then
+ v:getFocus()
+ v:setVisibility(true)
+ v:setActivity(true)
+ else
+ v:setVisibility(false)
+ v:setActivity(false)
end
end
+end
+function MenuSystem:lockMenu(menu, lock)
+ local lock = lock or true
+ if self:menuExist(menu) then
+ self.menus[menu]:lock(lock)
+ end
+end
+
+function MenuSystem:lockMenuVisibility(menu, lock)
+ local lock = lock or true
+ if self:menuExist(menu) then
+ self.menus[menu]:lockVisibility(lock)
+ end
+end
+
+function MenuSystem:setMenuActivity(menu, activity)
+ local activity = activity or true
+ if self:menuExist(menu) then
+ self.menus[menu]:setActivity(activity)
+ if activity == true then
+ -- if we make the menu active, he have to be visible too
+ self.menus[menu]:setVisibility(true)
+ end
+ end
+end
+
+function MenuSystem:setMenuVisibility(menu, visibility)
+ local visibility = visibility or true
+ if self:menuExist(menu) then
+ self.menus[menu]:setVisibility(visibility)
+ end
end
function MenuSystem:setAllMenuVisibility(visibility)
for k,v in pairs(self.menus) do
- v.isVisible = visibility
+ v:setVisibility(visibility)
end
end
@@ -67,45 +176,110 @@ function MenuSystem:removeDestroyedMenus()
end
end
-function MenuSystem:keyreleased(key, code)
- -- TODO:depreciated function
+-- SOUND FUNCTIONS
+-- Add sounds to every menus
+
+function MenuSystem:setSoundFromSceneAssets(soundname)
+ for k,v in pairs(self.menus) do
+ v:setSoundFromSceneAssets(soundname)
+ end
end
-function MenuSystem:mousemoved(x, y, dx, dy)
+function MenuSystem:setSound(soundasset)
for k,v in pairs(self.menus) do
- if v.isActive then
- 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)
- break;
+ v:setSound(soundasset)
+ end
+end
+
+-- UPDATE FUNCTIONS
+-- Update the menus of the menusystem
+
+function MenuSystem:update(dt)
+ if (self.isActive) then
+ self:removeDestroyedMenus()
+ for k,v in pairs(self.menus) do
+ v:update(dt)
+ v:updateWidgets(dt)
+ end
+
+ if self.menus[self.focusedMenu] ~= nil then
+ -- Only check buttons if the current focused menu is actually active
+ if self.menus[self.focusedMenu].isActive then
+ for k,v in pairs(self.keys) do
+ if self.keys[k].isPressed then
+ self.menus[self.focusedMenu]:keyreleased(k)
+ end
+ end
end
end
end
end
+-- MOUSE FUNCTIONS
+-- Send mouse inputs to the menu
+
+function MenuSystem:mousemoved(x, y, dx, dy)
+ if (self.isActive) then
+
+ for k,v in pairs(self.menus) do
+ if v.isActive then
+ 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)
+ break;
+ end
+ end
+ end
+
+ end
+end
+
function MenuSystem:mousepressed( x, y, button, istouch )
- for k,v in pairs(self.menus) do
- if v.isActive then
- 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 )
- break;
+ if (self.isActive) then
+ for k,v in pairs(self.menus) do
+ if v.isActive then
+ 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 )
+ break;
+ end
end
end
end
end
-function MenuSystem:draw(dt) -- On dessine les entitées
+-- DRAW FUNCTIONS
+-- All functions to draw the menus of the menusystem
+
+function MenuSystem:getDrawList()
+ local drawList = {}
for k,v in pairs(self.menus) do
- if (v.isVisible) then
- v:draw(dt)
+ local drawObject = {}
+ drawObject.name = k
+ drawObject.depth = v.depth
+ table.insert(drawList, drawObject)
+ end
+ table.sort(drawList, function(a,b) return a.depth > b.depth end)
+
+ return drawList
+end
+
+function MenuSystem:draw(dt)
+ if (self.isActive) then
+ -- Draw all the menus
+ self.drawList = self:getDrawList()
+
+ for i,v in ipairs(self.drawList) do
+ local v2 = self.menus[v.name]
+ if (v2.isVisible) then
+ v2:draw(dt)
+ end
+ end
+
+ if self.menus[self.focusedMenu] ~= nil then
+ if (self.menus[self.focusedMenu].isVisible) then
+ self.menus[self.focusedMenu]:drawCursor()
+ end
end
end
-
- if self.menus[self.focusedMenu] ~= nil then
- if (self.menus[self.focusedMenu].isVisible) then
- self.menus[self.focusedMenu]:drawCursor()
- end
- end
-
end
return MenuSystem
diff --git a/imperium-porcorum.love/core/modules/menusystem/listbox.lua b/imperium-porcorum.love/core/modules/menusystem/listbox.lua
index 944c465..19c2ea2 100644
--- a/imperium-porcorum.love/core/modules/menusystem/listbox.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/listbox.lua
@@ -1,9 +1,35 @@
+-- listbox : add a vertical list of widgets.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.listbox$', '') .. "."
local Menu = require(cwd .. "parent")
+local ListBox = Menu:extend()
+
local menuutils = require(cwd .. "widgets.utils")
-local ListBox = Menu:extend()
+-- INIT FUNCTIONS
+-- Initialize and configure functions.
function ListBox:new(menusystem, name, x, y, w, h, slotNumber)
self.view = {}
@@ -14,6 +40,9 @@ function ListBox:new(menusystem, name, x, y, w, h, slotNumber)
-- soit un multiple du nombre de slot et de leur hauteur
end
+-- UPDATE FUNCTIONS
+-- Update the menu every step.
+
function ListBox:updateWidgetSize()
self.widget.h = math.floor( self.h / self.view.slotNumber )
self.widget.w = self.w
@@ -36,6 +65,9 @@ function ListBox:updateView()
end
end
+-- KEYBOARD FUNCTIONS
+-- Handle input from keyboard/controllers.
+
function ListBox:keyreleased(key, code)
if key == 'up' then
@@ -48,18 +80,21 @@ function ListBox:keyreleased(key, code)
if key == "A" then
if (self.widget.selected >= 1 and self.widget.selected <= #self.widget.list) then
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("key")
end
end
if key == "B" then
if (self.widget.cancel >= 1 and self.widget.cancel <= #self.widget.list) then
- self.widget.list[self.widget.cancel]:action()
+ self.widget.list[self.widget.cancel]:action("key")
end
end
end
+-- MOUSE FUNCTIONS
+-- Handle input from pointers.
+
function ListBox:mousemoved(x, y)
local widget_selected = self.view.firstSlot + math.floor(y / self.widget.h)
@@ -76,12 +111,15 @@ function ListBox:mousepressed(x, y, button, isTouch)
self.widget.selected = widget_selected
self:getFocus()
if #self.widget.list > 0 then
- self.widget.list[self.widget.selected]:action()
+ self.widget.list[self.widget.selected]:action("pointer")
end
end
end
+-- DRAW FUNCTIONS
+-- draw the menu and the rest of content.
+
function ListBox:draw()
self:updateView()
local widgety = self.y
diff --git a/imperium-porcorum.love/core/modules/menusystem/parent.lua b/imperium-porcorum.love/core/modules/menusystem/parent.lua
index a6bb2a5..12777e6 100644
--- a/imperium-porcorum.love/core/modules/menusystem/parent.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/parent.lua
@@ -1,5 +1,39 @@
+-- parent.lua : The parent of the functions.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Menu = Object:extend()
+local function updateWidgetByOrder(a, b)
+ if a.order ~= b.order then
+ return a.order < b.order
+ else
+ return a.creationID < b.creationID
+ end
+end
+
+-- INIT FUNCTIONS
+-- Initialize and configure functions.
+
function Menu:new(menusystem, name, x, y, w, h)
self.menusystem = menusystem
self.name = name
@@ -16,17 +50,49 @@ function Menu:new(menusystem, name, x, y, w, h)
self.widget.cancel = 0
self:updateWidgetSize()
- self.isDestroyed = false
- self.isVisible = true
- self.isActive = true
+ self.isDestroyed = false
+ self.isVisible = true
+ self.isActive = true
+ self.isLocked = false
+ self.isAlwaysVisible = false
- self.sound = {}
- self.sound.asset = nil
- self.sound.active = false
+ self.depth = 0
+
+ self:resetSound()
self:register()
end
+function Menu:setDepth(depth)
+ self.depth = depth or 0
+end
+
+function Menu:setVisibility(visibility)
+ if self.isLocked == false and self.isAlwaysVisible == false then
+ -- if the menu is locked (thus is always active), it should also
+ -- be always visible.
+ self.isVisible = visibility
+ else
+ self.isVisible = true
+ end
+end
+
+function Menu:setActivity(activity)
+ if self.isLocked == false then
+ self.isActive = activity
+ else
+ self.isActive = true
+ end
+end
+
+function Menu:lock(lock)
+ self.isLocked = lock
+end
+
+function Menu:lockVisibility(lock)
+ self.isAlwaysVisible = lock
+end
+
function Menu:getFocus()
self.menusystem.focusedMenu = self.name
end
@@ -52,14 +118,17 @@ function Menu:getWidgetSize(id)
return self.widget.w, self.widget.h
end
-function Menu:cancelAction()
- if (self.widget.cancel ~= 0) then
- self.widget.list[self.widget.cancel]:action()
- end
+function Menu:getWidgetNumber()
+ return #self.widget.list
end
-function Menu:update(dt)
- -- Cette fonction ne contient rien par défaut
+-- ACTION FUNCTIONS
+-- Send actions to the widgets
+
+function Menu:cancelAction()
+ if (self.widget.cancel ~= 0) then
+ self.widget.list[self.widget.cancel]:action("key")
+ end
end
function Menu:clear()
@@ -80,6 +149,20 @@ function Menu:destroy()
self.destroyed = true
end
+function Menu:updateWidgetsOrder()
+ table.sort(self.widget.list, updateWidgetByOrder)
+end
+
+-- UPDATE FUNCTIONS
+-- Update the menu every game update
+
+function Menu:update(dt)
+ -- Cette fonction ne contient rien par défaut
+end
+
+-- DRAW FUNCTIONS
+-- Draw the menu and its content
+
function Menu:draw()
-- nothing here
end
@@ -92,10 +175,16 @@ function Menu:drawCanvas()
end
+-- KEYBOARD FUNCTIONS
+-- Handle key press
+
function Menu:keyreleased(key)
-- Cette fonction ne contient rien par défaut
end
+-- MOUSE FUNCTIONS
+-- Handle pointers (clic/touch)
+
function Menu:mousemoved(x, y)
-- Cette fonction ne contient rien par défaut
end
@@ -104,12 +193,16 @@ function Menu:mousepressed( x, y, button, istouch )
-- Cette fonction ne contient rien par défaut
end
+-- WIDGET FUNCTIONS
+-- Handle widgets of the functions
+
function Menu:addWidget(newwidget)
if #self.widget.list == 0 then
self.widget.selected = 1
end
table.insert(self.widget.list, newwidget)
self:updateWidgetsID()
+ self:updateWidgetsOrder()
end
function Menu:updateWidgets(dt)
@@ -134,12 +227,15 @@ function Menu:removeDestroyedWidgets() -- On retire les widgets marquées comme
end
end
+-- CURSOR FUNCTIONS
+-- Set or move the cursor of the menu
+
function Menu:setCursor(cursorid)
self.widget.selected = cursorid --math.max(1, math.min(cursorid, #self.widget.list))
end
function Menu:moveCursor(new_selected)
- self:playSelectSound()
+ self:playNavigationSound()
if new_selected < 1 then
self.widget.selected = #self.widget.list + new_selected
else
@@ -151,12 +247,25 @@ function Menu:moveCursor(new_selected)
end
end
+-- SOUND FUNCTION
+-- Handle SFX
+
+function Menu:resetSound()
+ self.sound = {}
+ self.sound.active = false
+ self.sound.asset = nil
+end
+
+function Menu:setSoundFromSceneAssets(name)
+ self:setSound(self.menusystem.scene.assets.sfx[name])
+end
+
function Menu:setSound(soundasset)
self.sound.active = true
self.sound.asset = soundasset
end
-function Menu:playSelectSound()
+function Menu:playNavigationSound()
if self.sound.active == true then
love.audio.stop( self.sound.asset )
self.sound.asset:setVolume(core.options.data.audio.sfx / 100)
@@ -164,6 +273,9 @@ function Menu:playSelectSound()
end
end
+-- VIEW FUNCTIONS
+-- Handle the view of the menu
+
function Menu:resetView()
-- ne sert à rien ici, c'est juste pour éviter des crash
end
diff --git a/imperium-porcorum.love/core/modules/menusystem/textmenu.lua b/imperium-porcorum.love/core/modules/menusystem/textmenu.lua
deleted file mode 100644
index 6703039..0000000
--- a/imperium-porcorum.love/core/modules/menusystem/textmenu.lua
+++ /dev/null
@@ -1,221 +0,0 @@
-local cwd = (...):gsub('%.textmenu$', '') .. "."
-local Menu = require(cwd .. "parent")
-
-local TextMenu = Menu:extend()
-
-function TextMenu:new(menusystem, name, x, y, font, slots)
- self.slots = slots
- TextMenu.super.new(self, menusystem, name, x, y, 0, 0)
- self.ox = x
- self.oy = y
- self.font = font
- self.align = "left"
-
- self.begin = 1
-
- self:getBoundingBox()
-end
-
-function TextMenu:getBoundingBox()
- self:setWidthAuto()
- self.widget.h = self.font:getHeight()
- self.h = self.widget.h * self.slots
-
- if self.align == "right" then
- self.x = self.ox - self.w
- elseif self.align == "center" then
- self.x = self.ox - self.w / 2
- else
- self.x = self.ox
- end
-
- self.y = self.oy
-end
-
-function TextMenu:centerText()
- self:setAlign("center")
-end
-
-function TextMenu:setAlign(align)
- self.align = align
- self.font:setAlign("center")
-end
-
-function TextMenu:update(dt)
- self:getBoundingBox()
- if self.widget.selected ~= 0 then
- if self.widget.selected < self.begin then
- self.begin = self.widget.selected
- end
- if self.widget.selected > self.begin + self.slots - 1 then
- self.begin = self.widget.selected - self.slots + 1
- end
- end
-
- if self.begin < 1 then
- self.begin = 1
- end
-end
-
-function TextMenu:setWidthAuto()
- local width = self.w
-
- for i,v in ipairs(self.widget.list) do
- local stringWidth = self.font:getWidth(v.beginlabel .. v.label .. v.endlabel)
- width = math.max(stringWidth, width)
- end
- if width ~= self.w then
- self.canvas.needRedraw = true
- end
- self.w = width
-end
-
-function TextMenu:getWidth()
- self:setWidthAuto()
- return self.w
-end
-
-function TextMenu:getHeight()
- return self.h
-end
-
-function TextMenu:keyreleased(key, code)
-
- if key == 'up' then
- self:moveCursor(self.widget.selected - 1)
- end
-
- if key == 'down' then
- self:moveCursor(self.widget.selected + 1)
- end
-
- if key == "A" then
- if (self.widget.selected > 0) and (self.widget.selected <= #self.widget.list) then
- self.widget.list[self.widget.selected]:action()
- end
- end
-
- if key == "B" then
- self:cancelAction()
- end
-
-end
-
-function TextMenu:mousemoved(x, y)
- local selectedPrevous = self.widget.selected
- self.widget.selected = self.begin + math.floor(y / self.widget.h)
- if self.widget.selected < 1 then
- self.widget.selected = 1
- end
- if self.widget.selected > #self.widget.list then
- self.widget.selected = #self.widget.list
- end
-
- if self.widget.selected ~= selectedPrevious then
- self.canvas.needRedraw = true
- end
-end
-
-function TextMenu:mousepressed(x, y, button, isTouch)
- self.widget.selected = self.begin + math.floor(y / self.widget.h)
- if self.widget.selected < 1 then
- self.widget.selected = 1
- end
- if self.widget.selected > #self.widget.list then
- self.widget.selected = #self.widget.list
- end
- if #self.widget.list > 0 then
- self.widget.list[self.widget.selected]:action()
- end
-
- if self.widget.selected ~= selectedPrevious then
- self.canvas.needRedraw = true
- end
-end
-
-function TextMenu:drawCanvas()
- print("redraw menu")
-
- self.canvas.texture = love.graphics.newCanvas(self.w, self.h)
- love.graphics.setCanvas( self.canvas.texture )
-
- local ox, x
-
- local widgety = 0
- local ox = self.w / 2
- local x = 0
-
- self.font:set()
- for i, v in ipairs(self.widget.list) do
- if (i >= self.begin) and (i < self.begin + self.slots) then
- self:drawWidget(i, widgety)
-
- widgety = widgety + self.widget.h
- end
- end
- utils.draw.resetColor()
-
- love.graphics.setCanvas( )
-end
-
-function TextMenu:drawWidget(widgetID, y)
- local widget = self.widget.list[widgetID]
- print(widget)
- if widget.canvas.needRedraw == true then
- self:drawWidgetCanvas(widget)
- widget.canvas.needRedraw = false
- end
-
- if self.widget.selected == widgetID and self.focus == true then
- love.graphics.setColor(1, 1, 0, 1)
- else
- love.graphics.setColor(1, 1, 1, 1)
- end
-
- love.graphics.draw(widget.canvas.texture, 0, y)
-end
-
-function TextMenu:drawWidgetCanvas(widget)
- widget.canvas.texture = love.graphics.newCanvas(self.w, self.widget.h)
-
- love.graphics.setCanvas( widget.canvas.texture )
-
- self.font:draw(widget.label, math.floor(self.w / 2), 0, -1, self.align)
- self.font:draw(widget.beginlabel, math.floor(0), 0, -1, "left")
- self.font:draw(widget.endlabel, math.floor(self.w), 0, -1, "right")
-
- love.graphics.setCanvas( self.canvas.texture )
-end
-
-function TextMenu:resetView()
- self.begin = 1
- self.canvas.needRedraw = true
-end
-
-function Menu:moveView(begin, absolute)
- --local absolute = absolute or true
- self.widget.selected = 0
-
- if (absolute) then
- self.begin = begin
- else
- self.begin = self.begin + begin
- end
- -- ne sert à rien ici, c'est juste pour éviter des crash
-
- self.canvas.needRedraw = true
-end
-
-function Menu:getView()
- return self.begin
-end
-
-function Menu:isViewAtBeggining()
- return (self.begin <= 1)
-end
-
-function Menu:isViewAtEnd()
- return ((self.begin + self.slots) > (#self.widget.list))
-end
-
-return TextMenu
diff --git a/imperium-porcorum.love/core/modules/menusystem/widgets/init.lua b/imperium-porcorum.love/core/modules/menusystem/widgets/init.lua
index 03006df..9c02e5a 100644
--- a/imperium-porcorum.love/core/modules/menusystem/widgets/init.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/widgets/init.lua
@@ -1,8 +1,34 @@
+-- widgets :: basic widget object
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Widget = {}
BaseWidget = Object:extend()
TextWidget = BaseWidget:extend()
+-- INIT FUNCTIONS
+-- Initialize and configure the widget
+
function BaseWidget:new(menu)
self.menu = menu
@@ -11,27 +37,37 @@ function BaseWidget:new(menu)
self.selection_margin = 0
self.margin = 2
- self:register()
-
self.canvas = {}
self.canvas.texture = nil
self.canvas.needRedraw = true
+
+ self.order = 0
+ self:register()
end
function BaseWidget:register()
+ self.creationID = self.menu:getWidgetNumber()
self.menu:addWidget(self)
end
function BaseWidget:redrawCanvas()
self.width, self.height = self.menu:getWidgetSize(self.id)
- self.canvas.texture = love.graphics.newCanvas(self.width, self.height)
- love.graphics.setCanvas( self.canvas.texture )
+ local canvas = love.graphics.newCanvas(self.width, self.height)
+ love.graphics.setCanvas( canvas )
self:drawCanvas()
self.canvas.needRedraw = false
love.graphics.setCanvas( )
+ local imageData = canvas:newImageData( )
+ self.canvas.texture = love.graphics.newImage( imageData )
+ canvas:release( )
+ imageData:release( )
+end
+
+function BaseWidget:invalidateCanvas()
+ self.canvas.needRedraw = true
end
function BaseWidget:drawCanvas()
@@ -50,6 +86,9 @@ function BaseWidget:selectAction()
-- Do nothing
end
+-- DRAW WIDGETS
+-- Draw the widget
+
function BaseWidget:draw(x, y)
if self.canvas.texture ~= nil then
utils.graphics.resetColor()
@@ -61,6 +100,9 @@ function BaseWidget:drawSelected(x,y,w,h)
self:draw(x, y, w, h)
end
+-- UPDATE FUNCTIONS
+-- Update the widget
+
function BaseWidget:update(dt)
if (self.canvas.needRedraw) then
self:redrawCanvas()
@@ -68,7 +110,10 @@ function BaseWidget:update(dt)
-- N/A
end
-function BaseWidget:action()
+-- ACTION FUNCTION
+-- Functions to handle actions and selection.
+
+function BaseWidget:action(source)
--self:destroy()
end
@@ -76,6 +121,7 @@ function BaseWidget:destroy()
self.destroyed = true
end
+-- TEXT WIDGET
-- Simple text widget
function TextWidget:new(menu, font, label)
@@ -91,7 +137,7 @@ function TextWidget:drawCanvas()
self.font:draw(self.label, w, h, -1, "center")
end
-
+-- Add the widget as subvariable to the returned table
Widget.Base = BaseWidget
Widget.Text = TextWidget
diff --git a/imperium-porcorum.love/core/modules/menusystem/widgets/utils.lua b/imperium-porcorum.love/core/modules/menusystem/widgets/utils.lua
index ab4a48c..e69c6d7 100644
--- a/imperium-porcorum.love/core/modules/menusystem/widgets/utils.lua
+++ b/imperium-porcorum.love/core/modules/menusystem/widgets/utils.lua
@@ -1,8 +1,31 @@
+-- widgets/utils :: basic utility functions for the widgets
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 menuUtils = {}
function menuUtils.drawCursor(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)
diff --git a/imperium-porcorum.love/core/modules/scenes.lua b/imperium-porcorum.love/core/modules/scenes.lua
index c87f29e..9da7c83 100644
--- a/imperium-porcorum.love/core/modules/scenes.lua
+++ b/imperium-porcorum.love/core/modules/scenes.lua
@@ -22,27 +22,64 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
+local cwd = (...):gsub('%.scenes$', '') .. "."
+
local Scene = Object:extend()
-local Assets = require "core.modules.assets"
-local MenuSystem = require "core.modules.menusystem"
+
+local Assets = require(cwd .. "assets")
+local MenuSystem = require(cwd .. "menusystem")
+
+-- INIT FUNCTIONS
+-- Initialize and configure the scene
function Scene:new()
self.mouse = {}
self.mouse.x, self.mouse.y = core.screen:getMousePosition()
self.assets = Assets()
- self.menusystem = MenuSystem()
- self.keys = core.input:getKeyList()
+ self.menusystem = MenuSystem(self)
+ self.sources = core.input:getSources()
+
+ self.inputLocked = false
+ self.inputLockedTimer = 0
+
+ self:initWorld()
+
+ self:register()
end
function Scene:register()
- core.scenemanager.currentScene = self
+ core.scenemanager:setScene(self)
+end
+
+function Scene:clear()
+ -- TODO: send automatic cleanups to the different elements of the scene
+end
+
+-- UPDATE FUNCTIONS
+-- Handle stuff that happens every steps
+
+function Scene:updateStart(dt)
+
end
function Scene:update(dt)
-- Empty function, is just here to avoid crash
end
+function Scene:updateEnd(dt)
+
+end
+
+function Scene:updateWorld(dt)
+ if (self.world ~= nil) and (self.world.isActive) then
+ self.world:update(dt)
+ end
+end
+
+-- MOUSE FUNCTIONS
+-- Make the scene support the pointer
+
function Scene:mousemoved(x, y, dx, dy)
-- Empty function, is just here to avoid crash
end
@@ -51,17 +88,84 @@ function Scene:mousepressed( x, y, button, istouch )
-- Empty function, is just here to avoid crash
end
+-- WORLD FUNCTIONS
+-- Basic functions to manage the world
+
+function Scene:initWorld()
+ self.world = nil
+end
+
+function Scene:registerWorld(world)
+ self.world = world
+end
+
+-- KEYBOARD FUNCTIONS
+-- Add send keys functions to the scene
+
+function Scene:keypressed( key, scancode, isrepeat )
+
+end
+
+function Scene:keyreleased( key )
+
+end
+
+-- DRAW FUNCTIONS
+-- Draw the scene and its content
+
+function Scene:drawStart()
+
+end
+
function Scene:draw()
end
-function Scene:clear()
+function Scene:drawEnd()
end
+function Scene:drawWorld(dt)
+ if (self.world ~= nil) then
+ self.world:draw()
+ end
+end
+
+-- INPUT FUNCTIONS
+-- Handle inputs from keyboard/controllers
+
+function Scene:setKeys()
+ if (self.inputLocked) then
+ self.sources = core.input.fakesources
+ self.inputLockedTimer = self.inputLockedTimer - 1
+ if (self.inputLockedTimer <= 0 ) then
+ self.inputLocked = false
+ end
+ else
+ self.sources = core.input.sources
+ end
+
+ self.menusystem.keys = self.sources[1].keys
+end
+
+function Scene:getKeys(sourceid)
+ if sourceid == nil then
+ print("WARNING", "no sourceid detected, will default to 1")
+ end
+
+ local sourceid = sourceid or 1
+ if (self.inputLocked) then
+ return self.sources[sourceid].keys
+ else
+ return core.input.fakekeys
+ end
+end
+
function Scene:flushKeys()
core.input:flushKeys()
- self.keys = core.input.keys
+ self.sources = core.input:getSources()
+ self.inputLockedTimer = 1
+ self.inputLocked = true
end
return Scene
diff --git a/imperium-porcorum.love/core/modules/world/actors/actor2D.lua b/imperium-porcorum.love/core/modules/world/actors/actor2D.lua
new file mode 100644
index 0000000..a6613d2
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/actors/actor2D.lua
@@ -0,0 +1,202 @@
+-- actor2D.lua :: the implementation of a 2D actor. It contain every element
+-- needed to create your own 2D actors.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.actor2D$', '') .. "."
+local BaseActor = require(cwd .. "baseactor")
+local Actor2D = BaseActor:extend()
+
+-- INIT FUNCTIONS
+-- Initialise the actor and its base functions
+
+function Actor2D:new(world, type, x, y, w, h, isSolid)
+ self:initHitbox(x, y, w, h)
+ Actor2D.super.new(self, world, type, isSolid)
+end
+
+-- MOVEMENT FUNCTIONS
+-- Basic functions from the movement.
+
+function Actor2D:initMovement()
+ self.xsp = 0
+ self.ysp = 0
+
+ self.xfrc = 0
+ self.yfrc = 0
+end
+
+function Actor2D:autoMove(dt)
+ self.onGround = false
+ self:applyGravity(dt)
+
+ local newx, newy, cols, colNumber = self:move(self.x + self.xsp * dt, self.y + self.ysp * dt)
+
+ -- apply after the movement the friction, until the player stop
+ -- note: the friction is applied according to the delta time,
+ -- thus the friction should be how much speed is substracted in 1 second
+
+ self:solveAllCollisions(cols)
+
+ self.xsp = utils.math.toZero(self.xsp, self.xfrc * dt)
+ self.ysp = utils.math.toZero(self.ysp, self.yfrc * dt)
+end
+
+function Actor2D:solveAllCollisions(cols)
+ for i, col in ipairs(cols) do
+ self:collisionResponse(col)
+ if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
+ self:changeSpeedToCollisionNormal(col.normal.x, col.normal.y)
+ end
+ end
+end
+
+function Actor2D:collisionResponse(collision)
+ -- here come the response to the collision
+end
+
+function Actor2D:changeSpeedToCollisionNormal(nx, ny)
+ local xsp, ysp = self.xsp, self.ysp
+
+ if (nx < 0 and xsp > 0) or (nx > 0 and xsp < 0) then
+ xsp = -xsp * self.bounceFactor
+ end
+
+ if (ny < 0 and ysp > 0) or (ny > 0 and ysp < 0) then
+ ysp = -ysp * self.bounceFactor
+ end
+
+ self.xsp, self.ysp = xsp, ysp
+end
+
+function Actor2D:checkGroundX()
+ local dx, dy = self.x + utils.math.sign(self.xgrav), self.y
+ local newx, newy, cols, colNumber = self:checkCollision(dx, dy)
+
+ for i, col in ipairs(cols) do
+ if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
+ if not (self.ygrav == 0) then
+ if col.normal.x ~= utils.math.sign(self.xgrav) then self.onGround = true end
+ end
+ end
+ end
+end
+
+function Actor2D:checkGroundY()
+ local dx, dy = self.x, self.y + utils.math.sign(self.ygrav)
+ local newx, newy, cols, colNumber = self:checkCollision(dx, dy)
+
+ for i, col in ipairs(cols) do
+ if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
+ if not (self.ygrav == 0) then
+ if col.normal.y ~= utils.math.sign(self.ygrav) then self.onGround = true end
+ end
+ end
+ end
+end
+
+function Actor2D:move(dx, dy)
+ local cols, colNumber = {}, 0
+ if (self.isDestroyed == false) then
+ self.x, self.y, cols, colNumber = self.world:moveActor(self, dx, dy, self.filter)
+ end
+ return self.x, self.y, cols, colNumber
+end
+
+function Actor2D:checkCollision(dx, dy)
+ local x, y, cols, colNumber = dx, dy, {}, 0
+ if (self.isDestroyed == false) then
+ x, y, cols, colNumber = self.world:moveActor(self, dx, dy, self.filter)
+ end
+ return self.x, self.y, cols, colNumber
+end
+
+function Actor2D:initGravity()
+ local xgrav, ygrav
+
+ if (self.world.gravity.isDefault) then
+ self.xgrav = self.world.gravity.xgrav
+ self.ygrav = self.world.gravity.ygrav
+ else
+ self.xgrav = 0
+ self.ygrav = 0
+ end
+
+ self.onGround = false
+end
+
+function Actor2D:setXGravity(grav)
+ self.xgrav = grav
+end
+
+function Actor2D:setYGravity(grav)
+ self.ygrav = grav
+end
+
+function Actor2D:applyGravity(dt)
+ self.xsp = self.xsp + self.xgrav * dt
+ self.ysp = self.ysp + self.ygrav * dt
+
+ if utils.math.sign(self.ysp) == utils.math.sign(self.ygrav) then
+ self:checkGroundY( )
+ end
+
+ if utils.math.sign(self.xsp) == utils.math.sign(self.xgrav) then
+ self:checkGroundX( )
+ end
+end
+
+-- COORDINATE FUNCTIONS
+-- Functions related to coordinate and hitbox
+
+function Actor2D:initHitbox(x, y, w, h)
+ self.x = x or 0
+ self.y = y or 0
+ self.w = w or 0
+ self.h = h or 0
+end
+
+function Actor2D:getCenter()
+ return (self.x + (self.w / 2)), (self.y + (self.h / 2))
+end
+
+function Actor2D:getViewCenter()
+ return self:getCenter()
+end
+
+function Actor2D:drawHitbox()
+ local x, y = math.floor(self.x), math.floor(self.y)
+ love.graphics.setColor(self.debug.r, self.debug.g, self.debug.b, 1)
+ utils.graphics.box(x, y, self.w, self.h)
+end
+
+-- DRAW FUNCTIONS
+-- Draw the actors.
+
+function Actor2D:draw()
+ self:drawStart()
+ local x, y = math.floor(self.x), math.floor(self.y)
+ self:drawSprite(x, y)
+ self:drawEnd()
+end
+
+return Actor2D
diff --git a/imperium-porcorum.love/core/modules/world/actors/baseactor.lua b/imperium-porcorum.love/core/modules/world/actors/baseactor.lua
new file mode 100644
index 0000000..5512449
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/actors/baseactor.lua
@@ -0,0 +1,253 @@
+-- actor2D.lua :: the global implementation of an actor. Basically, it abstract
+-- everything that isn't 2D or 3D related to the actor system.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.baseactor$', '') .. "."
+local BaseActor = Object:extend()
+
+local Timer = require(cwd .. "utils.timer")
+
+-- INIT FUNCTIONS
+-- Initialise the actor and its base functions
+
+function BaseActor:new(world, type, isSolid)
+ self.type = type or ""
+ self.isSolid = isSolid or false
+ self.depth = 0
+
+ self:setManagers(world)
+ self:initKeys()
+ self:initTimers()
+ self:setSprite()
+ self:initPhysics()
+
+ self:setDebugColor(1, 1, 1)
+ self:register()
+end
+
+function BaseActor:setManagers(world)
+ self.world = world
+ self.scene = world.scene
+ self.obj = world.obj
+ self.assets = self.scene.assets
+end
+
+function BaseActor:setDebugColor(r,g,b)
+ self.debug = {}
+ self.debug.r = r
+ self.debug.g = g
+ self.debug.b = b
+end
+
+function BaseActor:register()
+ self.world:registerActor(self)
+ self.isDestroyed = false
+end
+
+function BaseActor:destroy()
+ self.world:removeActor(self)
+ self.isDestroyed = true
+end
+
+-- PHYSICS INITIALISATION
+-- Basic initialization of the physic systems
+
+function BaseActor:initPhysics()
+ self:initMovement()
+ self:initGravity()
+
+ self:setBounceFactor()
+ self:setFilter()
+end
+
+function BaseActor:setBounceFactor(newBounceFactor)
+ self.bounceFactor = newBounceFactor or 0
+end
+
+function BaseActor:setFilter()
+ -- Init the bump filter
+ self.filter = function(item, other)
+ if (other.isSolid) then
+ return "slide"
+ else
+ return "cross"
+ end
+ end
+end
+
+function BaseActor:initMovement( )
+ -- Empty placeholder function
+end
+
+function BaseActor:initGravity( )
+ -- Empty placeholder function
+end
+
+
+-- UPDATE FUNCTIONS
+-- Theses functions are activated every steps
+
+function BaseActor:updateStart(dt)
+
+end
+
+function BaseActor:update(dt)
+ self:updateStart(dt)
+ self:updateTimers(dt)
+ self:autoMove(dt)
+ self:updateSprite(dt)
+ self:updateEnd(dt)
+end
+
+function BaseActor:updateEnd(dt)
+
+end
+
+function BaseActor:autoMove(dt)
+ -- The base actor don't have coordinate
+ -- so the autoMove is only usefull to its
+ -- 2D and 3D childrens
+end
+
+-- INPUT FUNCTIONS
+-- get input from the world object
+
+function BaseActor:initKeys()
+ self.keys = core.input.fakekeys
+end
+
+function BaseActor:getInput(keys)
+ self.keys = keys or core.input.fakekeys
+end
+
+-- TIMER FUNCTIONS
+-- Control the integrated timers of the actor
+
+function BaseActor:initTimers()
+ self.timers = {}
+end
+
+function BaseActor:addTimer(name, t)
+ self.timers[name] = Timer(self, name, t)
+end
+
+function BaseActor:updateTimers(dt)
+ for k,v in pairs(self.timers) do
+ v:update(dt)
+ end
+end
+
+function BaseActor:timerResponse(name)
+ -- here come the timer responses
+end
+
+-- DRAW FUNCTIONS
+-- Draw the actors.
+
+function BaseActor:drawStart()
+
+end
+
+function BaseActor:draw()
+ self:drawStart()
+ self:drawEnd()
+end
+
+function BaseActor:drawEnd()
+
+end
+
+function BaseActor:drawHUD(id, height, width)
+
+end
+
+
+-- SPRITES FUNCTIONS
+-- Handle the sprite of the actor
+
+function BaseActor:setSprite(spritename, ox, oy)
+ self.sprite = {}
+ self.sprite.name = spritename or nil
+ self.sprite.ox = ox or 0
+ self.sprite.oy = oy or 0
+ self.sprite.sx = 1
+ self.sprite.sy = 1
+ self.sprite.exist = (spritename ~= nil)
+ self.sprite.clone = nil
+end
+
+function BaseActor:cloneSprite()
+ if self.sprite.name ~= nil then
+ self.sprite.clone = self.assets.sprites[self.sprite.name]:clone()
+ end
+end
+
+function BaseActor:changeAnimation(animation, restart)
+ if (self.sprite.clone == nil) then
+ self.assets.sprites[self.sprite.name]:changeAnimation(animation, restart)
+ else
+ self.sprite.clone:changeAnimation(animation, restart)
+ end
+end
+
+function BaseActor:setCustomSpeed(customSpeed)
+ if (self.sprite.clone == nil) then
+ self.assets.sprites[self.sprite.name]:setCustomSpeed(customSpeed)
+ else
+ self.sprite.clone:setCustomSpeed(customSpeed)
+ end
+end
+
+function BaseActor:updateSprite(dt)
+ if (self.sprite.clone ~= nil) then
+ self.sprite.clone:update(dt)
+ end
+end
+
+function BaseActor:setSpriteScallingX(sx)
+ local sx = sx or 1
+
+ self.sprite.sx = sx
+end
+
+function BaseActor:setSpriteScallingY(sy)
+ local sy = sy or 1
+
+ self.sprite.sy = sy
+end
+
+function BaseActor:drawSprite(x, y, r, sx, sy, ox, oy, kx, ky)
+ if (self.sprite.name ~= nil) then
+ local x = x + self.sprite.ox
+ local y = y + self.sprite.oy
+ local sx = sx or self.sprite.sx
+ local sy = sy or self.sprite.sy
+ if (self.sprite.clone ~= nil) then
+ self.sprite.clone:draw(x, y, r, sx, sy, ox, oy, kx, ky)
+ else
+ self.assets.sprites[self.sprite.name]:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
+ end
+ end
+end
+
+return BaseActor
diff --git a/imperium-porcorum.love/core/modules/world/actors/gfx2D.lua b/imperium-porcorum.love/core/modules/world/actors/gfx2D.lua
new file mode 100644
index 0000000..9f3857e
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/actors/gfx2D.lua
@@ -0,0 +1,46 @@
+-- gfx.lua :: a basic 2D GFX.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.gfx2D$', '') .. "."
+local Actor2D = require(cwd .. "actor2D")
+local GFX = Actor2D:extend()
+
+function GFX:new(world, x, y, spritename)
+ local width, height = world.scene.assets.sprites[spritename]:getDimensions()
+
+ GFX.super.new(self, world, "gfx", x - (width/2), y - (height/2), width, height)
+ self:setSprite(spritename)
+ self:cloneSprite()
+
+ local duration = self.sprite.clone:getAnimationDuration()
+ self:addTimer("destroy", duration)
+ self.depth = -100
+end
+
+function GFX:timerResponse(name)
+ if (name == "destroy") then
+ self:destroy()
+ end
+end
+
+return GFX
diff --git a/imperium-porcorum.love/core/modules/world/actors/utils/timer.lua b/imperium-porcorum.love/core/modules/world/actors/utils/timer.lua
new file mode 100644
index 0000000..5ba7c3f
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/actors/utils/timer.lua
@@ -0,0 +1,44 @@
+-- timer.lua :: a basic implementation of a timer for the actor system.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Timer = Object:extend()
+
+function Timer:new(actor, name, t)
+ self.time = t
+ self.actor = actor
+ self.name = name
+end
+
+function Timer:update(dt)
+ self.time = self.time - dt
+ if (self.time <= 0) then
+ self:finish()
+ end
+end
+
+function Timer:finish()
+ self.actor:timerResponse(self.name)
+ self.actor.timers[self.name] = nil
+end
+
+return Timer
diff --git a/imperium-porcorum.love/core/modules/world/baseworld.lua b/imperium-porcorum.love/core/modules/world/baseworld.lua
new file mode 100644
index 0000000..4bf83eb
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/baseworld.lua
@@ -0,0 +1,437 @@
+-- baseworld.lua :: the base world object, that contain just a fast implementation
+-- of a 2D world. It doesn't support collision and stuff.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.baseworld$', '') .. "."
+
+local BaseWorld = Object:extend()
+
+local Sti = require(cwd .. "libs.sti")
+local CameraSystem = require(cwd .. "camera")
+
+local PADDING_VALUE = 10/100
+
+-- INIT FUNCTIONS
+-- All functions to init the world and the map
+
+function BaseWorld:new(scene, actorlist, mapfile)
+ self.scene = scene
+ self.actorlist = actorlist
+ self.mapfile = mapfile
+
+ self.cameras = CameraSystem(self)
+ self:initActors()
+
+ self:initPlayers()
+ self:setActorList(self.actorlist)
+ self:initMap(self.mapfile)
+ self:setGravity()
+
+ self:register()
+
+ self.isActive = true
+end
+
+function BaseWorld:setActorList(actorlist)
+ if actorlist == nil then
+ error("FATAL: You must set an actorlist to your world")
+ else
+ self.obj = require(actorlist)
+ end
+end
+
+function BaseWorld:initMap(mapfile)
+ self.haveMap = false
+ self.haveBackgroundColor = false
+ self.backcolor = {128, 128, 128}
+ self.mapfile = mapfile
+end
+
+function BaseWorld:setGravity(xgrav, ygrav, isDefault)
+ local xgrav = xgrav or 0
+ local ygrav = ygrav or 0
+ local isDefault = isDefault or 0
+
+ self.gravity = {}
+ self.gravity.xgrav, self.gravity.ygrav = xgrav, ygrav
+ self.gravity.isDefault = isDefault
+end
+
+function BaseWorld:register()
+ self.scene:registerWorld(self)
+end
+
+function BaseWorld:reset()
+ self:initActors()
+ self:initPlayers()
+ self:initMap(self.mapfile)
+ self.cameras:initViews()
+ collectgarbage()
+
+ self:loadMap()
+end
+
+-- ACTOR MANAGEMENT FUNCTIONS
+-- Basic function to handle actors
+
+function BaseWorld:initActors( )
+ self.actors = {}
+ self.currentCreationID = 0
+end
+
+function BaseWorld:newActor(name, x, y)
+ self.obj.index[name](self, x, y)
+end
+
+function BaseWorld:newCollision(name, x, y, w, h)
+ self.obj.collisions[name](self, x, y, w, h)
+end
+
+function BaseWorld:registerActor(actor)
+ actor.creationID = self.currentCreationID
+ self.currentCreationID = self.currentCreationID + 1
+ table.insert(self.actors, actor)
+end
+
+function BaseWorld:removeActor(actor)
+ for i,v in ipairs(self.actors) do
+ if v == actor then
+ table.remove(self.actors, i)
+ end
+ end
+end
+
+function BaseWorld:moveActor(actor, x, y, filter)
+ -- as the baseworld have no collision function, we return empty collision
+ -- datas, but from the same type than bump2D will return
+ return x, y, {}, 0
+end
+
+function BaseWorld:checkCollision(actor, x, y, filter)
+ -- as the baseworld have no collision function, we return empty collision
+ -- datas, but from the same type than bump2D will return
+ return x, y, {}, 0
+end
+
+function BaseWorld:queryRect(x, y, w, h)
+ local query = {}
+ local x2, y2 = x + w, y + h
+ for i,v in ipairs(self.actors) do
+ if (v.x >= x) and (v.x + v.w >= x1) and
+ (v.y >= y) and (v.y + v.h >= y1) then
+
+ table.insert(query, v)
+ end
+ end
+
+ return v
+end
+
+function BaseWorld:countActors()
+ return #self.actors
+end
+
+function BaseWorld:getActors()
+ return self.actors
+end
+
+function BaseWorld:getVisibleActors(id)
+ local camx, camy, camw, camh = self.cameras:getViewCoordinate(id)
+ local paddingw = camw * PADDING_VALUE
+ local paddingh = camh * PADDING_VALUE
+ local x = camx - paddingw
+ local y = camy - paddingh
+ local w = camw + paddingw * 2
+ local h = camh + paddingh * 2
+ return self:queryRect(x, y, w, h)
+end
+
+-- INFO FUNCTIONS
+-- Give infos about the world
+
+function BaseWorld:isActorIndexed(name)
+ return (self.obj.index[name] ~= nil)
+end
+
+function BaseWorld:isCollisionIndexed(name)
+ return (self.obj.collisions[name] ~= nil)
+end
+
+-- PLAYER MANAGEMENT FUNCTIONS
+-- Basic function to handle player actors
+
+function BaseWorld:loadMap()
+ local mapfile = self.mapfile
+ if mapfile == nil then
+ self.haveMap = false
+ self.haveBackgroundColor = false
+ self.backcolor = {128, 128, 128}
+ else
+ self.haveMap = true
+ self.map = Sti(mapfile)
+ self.haveBackgroundColor = true
+ self.backcolor = self.map.backgroundcolor or {128, 128, 128}
+ self:loadMapObjects()
+ end
+end
+
+function BaseWorld:initPlayers()
+ self.players = {}
+ self.playerNumber = 1
+end
+
+function BaseWorld:setPlayerNumber(playerNumber)
+ self.playerNumber = playerNumber or 1
+end
+
+function BaseWorld:addPlayer(actor, sourceid, haveCam)
+ local player = {}
+ player.actor = actor
+ player.sourceid = sourceid or 1
+
+ table.insert(self.players, player)
+
+ if (haveCam) then
+ local xx, yy = player.actor:getViewCenter()
+ self.cameras:addView(xx, yy, player.actor)
+ end
+end
+
+function BaseWorld:sendInputToPlayers(actor)
+ for i,v in ipairs(self.players) do
+ --TODO: make the player get from a selected source inputs
+ local keys = self.scene.sources[v.sourceid].keys
+ v.actor:getInput(keys)
+ end
+end
+
+function BaseWorld:removePlayer(actor)
+ for i,v in ipairs(self.players) do
+ if (v.actor == actor) then
+ table.remove(self.players, i)
+ end
+ end
+end
+
+-- MAP FUNCTIONS
+-- All map wrappers
+
+function BaseWorld:loadMapObjects()
+ self:loadMapCollisions()
+ self:loadMapPlayers()
+ self:loadMapActors()
+end
+
+function BaseWorld:loadMapCollisions()
+ for k, objectlayer in pairs(self.map.layers) do
+ if self:isCollisionIndexed(objectlayer.name) then
+ print("DEBUG: loading actors in " .. objectlayer.name .. " collision layer")
+ for k, object in pairs(objectlayer.objects) do
+ self:newCollision(objectlayer.name, object.x, object.y, object.width, object.height)
+ end
+ self.map:removeLayer(objectlayer.name)
+ end
+ end
+end
+
+function BaseWorld:loadMapActors()
+ for k, objectlayer in pairs(self.map.layers) do
+ if self:isActorIndexed(objectlayer.name) then
+ print("DEBUG: loading actors in " .. objectlayer.name .. " actor layer")
+ for k, object in pairs(objectlayer.objects) do
+ if (object.properties.batchActor) then
+ self:batchActor(objectlayer.name, object)
+ else
+ self:newActor(objectlayer.name, object.x, object.y)
+ end
+ end
+ self.map:removeLayer(objectlayer.name)
+ end
+ end
+end
+
+function BaseWorld:batchActor(name, object)
+ local gwidth = object.properties.gwidth or self.map.tilewidth
+ local gheight = object.properties.gheight or self.map.tileheight
+ local x = object.x
+ local y = object.y
+ local w = object.width
+ local h = object.height
+
+ local cellHor = math.ceil(w / gwidth)
+ local cellVert = math.ceil(h / gheight)
+
+ for i=1, cellHor do
+ for j=1, cellVert do
+ self:newActor(name, x + (i-1)*gwidth, y + (j-1)*gheight)
+ end
+ end
+end
+
+function BaseWorld:loadMapPlayers()
+ for k, objectlayer in pairs(self.map.layers) do
+ if (objectlayer.name == "player") then
+ print("DEBUG: loading actors in player layer")
+ local i = 1
+ for k, object in pairs(objectlayer.objects) do
+ if (i <= self.playerNumber) then
+ -- TODO: don't hardcode camera handling
+ self:addPlayer(self.obj.Player(self, object.x, object.y), i, true)
+ end
+ i = i + 1
+ end
+ self.map:removeLayer(objectlayer.name)
+ end
+ end
+end
+
+function BaseWorld:getDimensions()
+ if self.haveMap then
+ return self.map.width * self.map.tilewidth,
+ self.map.height * self.map.tileheight
+ else
+ return core.screen:getDimensions()
+ end
+end
+
+function BaseWorld:setBackgroundColor(r, g, b)
+ self.backcolor = {r, g, b}
+ self.haveBackgroundColor = true
+end
+
+function BaseWorld:removeBackgroundColor()
+ self.haveBackgroundColor = false
+end
+
+function BaseWorld:getBackgroundColor()
+ return self.backcolor[1]/256, self.backcolor[2]/256, self.backcolor[3]/256
+end
+
+-- Lock MANAGEMENT FUNCTIONS
+-- Basic function to handle the lock
+
+function BaseWorld:setActivity(activity)
+ self.isActive = activity
+end
+
+function BaseWorld:switchActivity()
+ self.isActive = (self.isActive == false)
+end
+
+function BaseWorld:getActivity()
+ return self.isActive
+end
+
+-- UPDATE FUNCTIONS
+-- All update functions
+
+function BaseWorld:update(dt)
+ self:updateMap(dt)
+ self:sendInputToPlayers(dt)
+ self:updateActors(dt)
+ self.cameras:update(dt)
+end
+
+function BaseWorld:updateActors(dt)
+ local actors = self:getActors()
+ for i,v in ipairs(actors) do
+ v:update(dt)
+ end
+end
+
+function BaseWorld:updateMap(dt)
+ if self.haveMap then
+ self.map:update(dt)
+ end
+end
+
+
+-- DRAW FUNCTIONS
+-- All function to draw the map, world and actors
+
+function BaseWorld:draw(dt)
+ self:drawBackgroundColor()
+ local camNumber = self.cameras:getViewNumber()
+
+ if (camNumber == 0) then
+ self:drawMap()
+ self:drawActors()
+ else
+ for i=1, camNumber do
+ self.cameras:attachView(i)
+ self:drawMap(i)
+ self:drawActors(i)
+ self.cameras:detachView(i)
+ self.cameras:drawHUD(i)
+ end
+ end
+end
+
+function BaseWorld:drawActors(id)
+ local actors
+ if (id == nil) then
+ actors = self:getActors()
+ else
+ actors = self:getVisibleActors(id)
+ end
+
+ table.sort(actors, function(a,b)
+ if (a.depth == b.depth) then
+ return a.creationID < b.creationID
+ else
+ return a.depth > b.depth
+ end
+ end)
+
+ for i,v in ipairs(actors) do
+ v:draw()
+ end
+end
+
+function BaseWorld:drawMap(id)
+ local tx, ty, scale = 0, 0, 1
+ if id ~= nil then
+ -- Du à la manière dont fonctionne STI, on est obligé de récupérer les info
+ -- de position de camera pour afficher la carte par rapport à ces infos
+ tx, ty = self.cameras:getViewCoordinate(id)
+ scale = self.cameras:getViewScale(id) or 1
+ local vx, vy = self.cameras:getOnScreenViewRelativePosition(id)
+ tx = math.floor(tx - math.abs(vx))
+ ty = math.floor(ty - math.abs(vy))
+ end
+
+ if self.haveMap then
+ self.map:draw(-tx, -ty, scale, scale)
+ end
+end
+
+function BaseWorld:drawBackgroundColor()
+ if self.haveBackgroundColor then
+ local r, g, b = self.backcolor[1], self.backcolor[2], self.backcolor[3]
+ love.graphics.setColor(r/256, g/256, b/256)
+ love.graphics.rectangle("fill", 0, 0, 480, 272)
+ utils.graphics.resetColor()
+ end
+end
+
+return BaseWorld
diff --git a/imperium-porcorum.love/core/modules/world/camera.lua b/imperium-porcorum.love/core/modules/world/camera.lua
new file mode 100644
index 0000000..db77abe
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/camera.lua
@@ -0,0 +1,337 @@
+-- camera.lua :: a basic camera adapted to the asset/world system.
+-- Use hump.camera as the view backend.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.camera$', '') .. "."
+
+local CameraSystem = Object:extend()
+local View = require(cwd .. "libs.hump.camera")
+
+local SPLITSCREEN_ISVERTICAL = false
+local SCREEN_LIMIT = 4
+
+-- INIT FUNCTIONS
+-- Initialize the camera system
+
+function CameraSystem:new(world)
+ self.scene = world.scene
+ self.world = world
+
+ self.verticalSplit = SPLITSCREEN_ISVERTICAL
+
+ self:initViews()
+end
+
+function CameraSystem:initViews()
+ self.views = {}
+
+ self.views.list = {}
+ self.views.basewidth, self.views.baseheight = core.screen:getDimensions()
+ self.views.width, self.views.height = self:getViewsDimensions()
+
+ self.views.posList = {}
+ self.views.posList.dual = {}
+ self.views.posList.multi = {}
+
+ if (self.verticalSplit) then
+ self.views.posList.dual[1] = {}
+ self.views.posList.dual[1].x = 0
+ self.views.posList.dual[1].y = (self.views.baseheight/4)
+
+ self.views.posList.dual[2] = {}
+ self.views.posList.dual[2].x = 0
+ self.views.posList.dual[2].y = -(self.views.baseheight/4)
+ else
+ self.views.posList.dual[1] = {}
+ self.views.posList.dual[1].x = -(self.views.basewidth/4)
+ self.views.posList.dual[1].y = 0
+
+ self.views.posList.dual[2] = {}
+ self.views.posList.dual[2].x = (self.views.basewidth/4)
+ self.views.posList.dual[2].y = 0
+ end
+
+ self.views.posList.multi[1] = {}
+ self.views.posList.multi[1].x = -(self.views.basewidth /4)
+ self.views.posList.multi[1].y = (self.views.baseheight/4)
+
+ self.views.posList.multi[2] = {}
+ self.views.posList.multi[2].x = (self.views.basewidth /4)
+ self.views.posList.multi[2].y = (self.views.baseheight/4)
+
+ self.views.posList.multi[3] = {}
+ self.views.posList.multi[3].x = -(self.views.basewidth /4)
+ self.views.posList.multi[3].y = -(self.views.baseheight/4)
+
+ self.views.posList.multi[4] = {}
+ self.views.posList.multi[4].x = (self.views.basewidth /4)
+ self.views.posList.multi[4].y = -(self.views.baseheight/4)
+end
+
+-- INFO FUNCTIONS
+-- Get informations from the camera system
+
+function CameraSystem:getViewNumber()
+ return #self.views.list
+end
+
+function CameraSystem:haveView()
+ return (self:getViewNumber() == 0)
+end
+
+function CameraSystem:getViewsDimensions(viewNumber)
+ local basewidth, baseheight = self.views.basewidth, self.views.baseheight
+ local viewnumber = viewNumber or self:getViewNumber()
+
+ if (viewnumber <= 1) then
+ return basewidth, baseheight
+ elseif (viewnumber == 2) then
+ if (self.verticalSplit) then
+ return (basewidth), (baseheight/2)
+ else
+ return (basewidth/2), (baseheight)
+ end
+ else
+ return (basewidth/2), (baseheight/2)
+ end
+end
+
+function CameraSystem:recalculateViewsPositions()
+ if #self.views.list == 1 then
+ self.views.list[1].pos.onScreen.x = 0
+ self.views.list[1].pos.onScreen.y = 0
+ else
+ for i,v in ipairs(self.views.list) do
+ local x, y = self:getViewPositions(i)
+ self.views.list[i].pos.onScreen.x = x
+ self.views.list[i].pos.onScreen.y = y
+ end
+ end
+end
+
+function CameraSystem:getViewPositions(id)
+ local viewNumber = #self.views.list
+
+ if (viewNumber == 2) and ((id == 1) or (id == 2)) then
+ return self.views.posList.dual[id].x, self.views.posList.dual[id].y
+ elseif (viewNumber > 2) then
+ return self.views.posList.multi[id].x, self.views.posList.multi[id].y
+ end
+
+end
+
+-- WRAPPER and UTILS
+-- Access data from the views
+
+function CameraSystem:addView(x, y, target)
+ if (#self.views.list < SCREEN_LIMIT) then
+ local id = #self.views.list + 1
+ local view = {}
+
+ view.pos = {}
+ view.pos.x = x or 0
+ view.pos.y = y or 0
+ view.pos.onScreen = {}
+
+ view.cam = View(view.pos.x, view.pos.y, 1, 0, true)
+ -- TODO: add a target system in order to make a camera able
+ -- to target a specific object
+ view.target = target
+
+ table.insert(self.views.list, view)
+ self.views.width, self.views.height = self:getViewsDimensions()
+ self:recalculateViewsPositions()
+ end
+end
+
+function CameraSystem:getView(id)
+ return self.views.list[id]
+end
+
+function CameraSystem:getViewCam(id)
+ local view = self:getView(id)
+
+ return view.cam
+end
+
+function CameraSystem:attachView(id)
+ if (id ~= nil) then
+ local cam = self:getViewCam(id)
+
+ local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
+ cam:attach()
+ love.graphics.setScissor(viewx, viewy, vieww, viewh)
+ end
+end
+
+function CameraSystem:detachView(id)
+ if (id ~= nil) then
+ local cam = self:getViewCam(id)
+
+ love.graphics.setScissor( )
+ cam:detach()
+ end
+end
+
+function CameraSystem:getViewCoordinate(id)
+ local cam = self:getViewCam(id)
+
+ local camx, camy, camw, camh
+ camx = cam.x - (self.views.width/2)
+ camy = cam.y - (self.views.height/2)
+
+ camw = self.views.width
+ camh = self.views.height
+ return camx, camy, camw, camh
+end
+
+function CameraSystem:getOnScreenViewCoordinate(id)
+ local view = self:getView(id)
+
+ local viewx, viewy, vieww, viewh
+ local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
+ viewx = (basex) + view.pos.onScreen.x - (self.views.width / 2)
+ viewy = (basey) + view.pos.onScreen.y - (self.views.height / 2)
+
+ vieww = self.views.width
+ viewh = self.views.height
+ return viewx, viewy, vieww, viewh
+end
+
+function CameraSystem:getOnScreenViewRelativePosition(id)
+ local view = self:getView(id)
+
+ local viewx, viewy
+ local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
+ viewx = view.pos.onScreen.x
+ viewy = view.pos.onScreen.y
+
+ return viewx, viewy
+end
+
+function CameraSystem:getOnScreenViewCenter(id)
+ local view = self:getView(id)
+
+ local viewx, viewy
+ local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
+ viewx = (basex) + view.pos.onScreen.x
+ viewy = (basey) + view.pos.onScreen.y
+
+ return viewx, viewy
+end
+
+function CameraSystem:getViewScale(id)
+ local cam = self:getViewCam(id)
+
+ return cam.scale
+end
+
+function CameraSystem:limitView(id)
+ local viewx, viewy, vieww, viewh = self:getViewCoordinate(id)
+ local worldw, worldh = self.world:getDimensions()
+ local posx = self.views.list[id].pos.x
+ local posy = self.views.list[id].pos.y
+ local minx = self.views.width / 2
+ local miny = self.views.height / 2
+ local maxx = worldw - minx
+ local maxy = worldh - miny
+
+ self.views.list[id].pos.x = utils.math.between(posx, minx, maxx)
+ self.views.list[id].pos.y = utils.math.between(posy, miny, maxy)
+
+ self:computeCamPosition(id)
+end
+
+-- UPDATE and MOVE functions
+-- Move and update the camera system
+
+function CameraSystem:update(dt)
+ for i,v in ipairs(self.views.list) do
+ self:followActor(i)
+ end
+end
+
+function CameraSystem:moveView(id, x, y)
+ self.views.list[id].pos.x = x
+ self.views.list[id].pos.y = y
+
+ self:computeCamPosition(id)
+ self:limitView(id)
+end
+
+function CameraSystem:computeCamPosition(id)
+ local decalx = self.views.list[id].pos.onScreen.x
+ local decaly = self.views.list[id].pos.onScreen.y
+
+ local realx = self.views.list[id].pos.x
+ local realy = self.views.list[id].pos.y
+
+ self.views.list[id].cam.x = realx - decalx
+ self.views.list[id].cam.y = realy + decaly
+ -- FIXME: this workaround certainly will cause some problem but that's the only
+ -- solution we seem to have right now
+ -- We invert the y decalage for the camera in order to work around a problem
+ -- that invert the y between it and the clipping.
+end
+
+function CameraSystem:followActor(id)
+ local view = self:getView(id)
+
+ if view.target ~= nil then
+ local x, y = view.target:getViewCenter()
+ x = math.floor(x)
+ y = math.floor(y)
+ self:moveView(id, x, y)
+ end
+end
+
+-- DRAW FUNCTIONS
+-- Basic callback to draw stuff
+
+function CameraSystem:drawDebugViewBox(id)
+ local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
+ utils.graphics.box(viewx, viewy, vieww, viewh)
+
+ local xx, yy = self:getOnScreenViewCenter(id)
+ love.graphics.line(xx-3, yy, xx+3, yy)
+ love.graphics.line(xx, yy-3, xx, yy+3)
+ local string = id .. " x:" .. viewx .. " y:" .. viewy
+ love.graphics.print(string, viewx + 4, viewy + 4)
+ print(viewy)
+end
+
+function CameraSystem:drawHUD(id)
+ local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
+ local view = self:getView(id)
+ local string2 = id .. " (" .. viewx .. ":" .. (viewh-viewy) .. ") "
+
+
+ love.graphics.setScissor(viewx, viewh-viewy, vieww, viewh)
+ love.graphics.translate(viewx, viewh-viewy)
+ view.target:drawHUD(id, vieww, viewh)
+
+ love.graphics.translate(-viewx, -(viewh-viewy))
+ love.graphics.setScissor( )
+end
+
+return CameraSystem
diff --git a/imperium-porcorum.love/core/modules/world/libs/bump-3dpd.lua b/imperium-porcorum.love/core/modules/world/libs/bump-3dpd.lua
new file mode 100644
index 0000000..f094f8d
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/bump-3dpd.lua
@@ -0,0 +1,947 @@
+local bump = {
+ _VERSION = 'bump-3dpd v0.2.0',
+ _URL = 'https://github.com/oniietzschan/bump-3dpd',
+ _DESCRIPTION = 'A 3D 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 assertIsCube(x,y,z,w,h,d)
+ assertType('number', x, 'x')
+ assertType('number', y, 'y')
+ assertType('number', z, 'z')
+ assertIsPositiveNumber(w, 'w')
+ assertIsPositiveNumber(h, 'h')
+ assertIsPositiveNumber(d, 'd')
+end
+
+local defaultFilter = function()
+ return 'slide'
+end
+
+------------------------------------------
+-- Cube functions
+------------------------------------------
+
+local function cube_getNearestCorner(x,y,z,w,h,d, px, py, pz)
+ return nearest(px, x, x + w),
+ nearest(py, y, y + h),
+ nearest(pz, z, z + d)
+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 cube
+-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
+local function cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1,x2,y2,z2, ti1,ti2)
+ ti1, ti2 = ti1 or 0, ti2 or 1
+ local dx = x2 - x1
+ local dy = y2 - y1
+ local dz = z2 - z1
+ local nx, ny, nz
+ local nx1, ny1, nz1, nx2, ny2, nz2 = 0,0,0,0,0,0
+ local p, q, r
+
+ for side = 1,6 do
+ if side == 1 then -- Left
+ nx,ny,nz,p,q = -1, 0, 0, -dx, x1 - x
+ elseif side == 2 then -- Right
+ nx,ny,nz,p,q = 1, 0, 0, dx, x + w - x1
+ elseif side == 3 then -- Top
+ nx,ny,nz,p,q = 0, -1, 0, -dy, y1 - y
+ elseif side == 4 then -- Bottom
+ nx,ny,nz,p,q = 0, 1, 0, dy, y + h - y1
+ elseif side == 5 then -- Front
+ nx,ny,nz,p,q = 0, 0, -1, -dz, z1 - z
+ else -- Back
+ nx,ny,nz,p,q = 0, 0, 1, dz, z + d - z1
+ 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,nz1 = r, nx,ny,nz
+ end
+ else -- p > 0
+ if r < ti1 then
+ return nil
+ elseif r < ti2 then
+ ti2, nx2,ny2,nz2 = r,nx,ny,nz
+ end
+ end
+ end
+ end
+
+ return ti1,ti2, nx1,ny1,nz1, nx2,ny2,nz2
+end
+
+-- Calculates the minkowsky difference between 2 cubes, which is another cube
+local function cube_getDiff(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
+ return x2 - x1 - w1,
+ y2 - y1 - h1,
+ z2 - z1 - d1,
+ w1 + w2,
+ h1 + h2,
+ d1 + d2
+end
+
+local function cube_containsPoint(x,y,z,w,h,d, px,py,pz)
+ return px - x > DELTA
+ and py - y > DELTA
+ and pz - z > DELTA
+ and x + w - px > DELTA
+ and y + h - py > DELTA
+ and z + d - pz > DELTA
+end
+
+local function cube_isIntersecting(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
+ return x1 < x2 + w2 and x2 < x1 + w1 and
+ y1 < y2 + h2 and y2 < y1 + h1 and
+ z1 < z2 + d2 and z2 < z1 + d1
+end
+
+local function cube_getCubeDistance(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
+ local dx = x1 - x2 + (w1 - w2)/2
+ local dy = y1 - y2 + (h1 - h2)/2
+ local dz = z1 - z2 + (d1 - d2)/2
+ return (dx * dx) + (dy * dy) + (dz * dz)
+end
+
+local function cube_detectCollision(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2, goalX, goalY, goalZ)
+ goalX = goalX or x1
+ goalY = goalY or y1
+ goalZ = goalZ or z1
+
+ local dx = goalX - x1
+ local dy = goalY - y1
+ local dz = goalZ - z1
+ local x,y,z,w,h,d = cube_getDiff(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
+
+ local overlaps, ti, nx, ny, nz
+
+ if cube_containsPoint(x,y,z,w,h,d, 0,0,0) then -- item was intersecting other
+ local px, py, pz = cube_getNearestCorner(x,y,z,w,h,d, 0,0,0)
+ -- Volume of intersection:
+ local wi = min(w1, abs(px))
+ local hi = min(h1, abs(py))
+ local di = min(d1, abs(pz))
+ ti = wi * hi * di * -1 -- ti is the negative volume of intersection
+ overlaps = true
+ else
+ local ti1,ti2,nx1,ny1,nz1 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, 0,0,0,dx,dy,dz, -math.huge, math.huge)
+
+ -- item tunnels into other
+ if ti1
+ and ti1 < 1
+ and (abs(ti1 - ti2) >= DELTA) -- special case for cube going through another cube's corner
+ and (0 < ti1 + DELTA
+ or 0 == ti1 and ti2 > 0)
+ then
+ ti, nx, ny, nz = ti1, nx1, ny1, nz1
+ overlaps = false
+ end
+ end
+
+ if not ti then
+ return
+ end
+
+ local tx, ty, tz
+
+ if overlaps then
+ if dx == 0 and dy == 0 and dz == 0 then
+ -- intersecting and not moving - use minimum displacement vector
+ local px, py, pz = cube_getNearestCorner(x,y,z,w,h,d, 0,0,0)
+ if abs(px) <= abs(py) and abs(px) <= abs(pz) then
+ -- X axis has minimum displacement
+ py, pz = 0, 0
+ elseif abs(py) <= abs(pz) then
+ -- Y axis has minimum displacement
+ px, pz = 0, 0
+ else
+ -- Z axis has minimum displacement
+ px, py = 0, 0
+ end
+ nx, ny, nz = sign(px), sign(py), sign(pz)
+ tx = x1 + px
+ ty = y1 + py
+ tz = z1 + pz
+ else
+ -- intersecting and moving - move in the opposite direction
+ local ti1, _
+ ti1,_,nx,ny,nz = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, 0,0,0,dx,dy,dz, -math.huge, 1)
+ if not ti1 then
+ return
+ end
+ tx = x1 + dx * ti1
+ ty = y1 + dy * ti1
+ tz = z1 + dz * ti1
+ end
+ else -- tunnel
+ tx = x1 + dx * ti
+ ty = y1 + dy * ti
+ tz = z1 + dz * ti
+ end
+
+ return {
+ overlaps = overlaps,
+ ti = ti,
+ move = {x = dx, y = dy, z = dz},
+ normal = {x = nx, y = ny, z = nz},
+ touch = {x = tx, y = ty, z = tz},
+ itemCube = {x = x1, y = y1, z = z1, w = w1, h = h1, d = d1},
+ otherCube = {x = x2, y = y2, z = z2, w = w2, h = h2, d = d2},
+ }
+end
+
+------------------------------------------
+-- Grid functions
+------------------------------------------
+
+local function grid_toWorld(cellSize, cx, cy, cz)
+ return (cx - 1) * cellSize,
+ (cy - 1) * cellSize,
+ (cz - 1) * cellSize
+end
+
+local function grid_toCell(cellSize, x, y, z)
+ return floor(x / cellSize) + 1,
+ floor(y / cellSize) + 1,
+ floor(z / 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,z1,x2,y2,z2, f)
+ local cx1, cy1, cz1 = grid_toCell(cellSize, x1, y1, z1)
+ local cx2, cy2, cz2 = grid_toCell(cellSize, x2, y2, z2)
+ local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
+ local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
+ local stepZ, dz, tz = grid_traverse_initStep(cellSize, cz1, z1, z2)
+ local cx, cy, cz = cx1, cy1, cz1
+
+ f(cx, cy, cz)
+
+ -- 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) + abs(cz - cz2) > 1 do
+ if tx < ty and tx < tz then -- tx is smallest
+ tx = tx + dx
+ cx = cx + stepX
+ f(cx, cy, cz)
+ elseif ty < tz then -- ty is smallest
+ -- Addition: include both cells when going through corners
+ if tx == ty then
+ f(cx + stepX, cy, cz)
+ end
+ ty = ty + dy
+ cy = cy + stepY
+ f(cx, cy, cz)
+ else -- tz is smallest
+ -- Addition: include both cells when going through corners
+ if tx == tz then
+ f(cx + stepX, cy, cz)
+ end
+ if ty == tz then
+ f(cx, cy + stepY, cz)
+ end
+ tz = tz + dz
+ cz = cz + stepZ
+ f(cx, cy, cz)
+ end
+ end
+
+ -- If we have not arrived to the last cell, use it
+ if cx ~= cx2 or cy ~= cy2 or cz ~= cz2 then
+ f(cx2, cy2, cz2)
+ end
+end
+
+local function grid_toCellCube(cellSize, x,y,z,w,h,d)
+ local cx,cy,cz = grid_toCell(cellSize, x, y, z)
+ local cx2 = ceil((x + w) / cellSize)
+ local cy2 = ceil((y + h) / cellSize)
+ local cz2 = ceil((z + d) / cellSize)
+
+ return cx,
+ cy,
+ cz,
+ cx2 - cx + 1,
+ cy2 - cy + 1,
+ cz2 - cz + 1
+end
+
+------------------------------------------
+-- Responses
+------------------------------------------
+
+local touch = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+ return col.touch.x, col.touch.y, col.touch.z, {}, 0
+end
+
+local cross = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+ local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+
+ return goalX, goalY, goalZ, cols, len
+end
+
+local slide = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+ goalZ = goalZ or z
+
+ local tch, move = col.touch, col.move
+ if move.x ~= 0 or move.y ~= 0 or move.z ~= 0 then
+ if col.normal.x ~= 0 then
+ goalX = tch.x
+ end
+ if col.normal.y ~= 0 then
+ goalY = tch.y
+ end
+ if col.normal.z ~= 0 then
+ goalZ = tch.z
+ end
+ end
+
+ col.slide = {x = goalX, y = goalY, z = goalZ}
+
+ x, y, z = tch.x, tch.y, tch.z
+ local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+
+ return goalX, goalY, goalZ, cols, len
+end
+
+local bounce = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+ goalZ = goalZ or z
+
+ local tch, move = col.touch, col.move
+ local tx, ty, tz = tch.x, tch.y, tch.z
+ local bx, by, bz = tx, ty, tz
+
+ if move.x ~= 0 or move.y ~= 0 or move.z ~= 0 then
+ local bnx = goalX - tx
+ local bny = goalY - ty
+ local bnz = goalZ - tz
+
+ if col.normal.x ~= 0 then
+ bnx = -bnx
+ end
+ if col.normal.y ~= 0 then
+ bny = -bny
+ end
+ if col.normal.z ~= 0 then
+ bnz = -bnz
+ end
+
+ bx = tx + bnx
+ by = ty + bny
+ bz = tz + bnz
+ end
+
+ col.bounce = {x = bx, y = by, z = bz}
+ x, y, z = tch.x, tch.y, tch.z
+ goalX, goalY, goalZ = bx, by, bz
+
+ local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
+
+ return goalX, goalY, goalZ, 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.itemCube, a.otherCube, b.otherCube
+ local ad = cube_getCubeDistance(ir.x,ir.y,ir.z,ir.w,ir.h,ir.d, ar.x,ar.y,ar.z,ar.w,ar.h,ar.d)
+ local bd = cube_getCubeDistance(ir.x,ir.y,ir.z,ir.w,ir.h,ir.d, br.x,br.y,br.z,br.w,br.h,br.d)
+ return ad < bd
+ end
+ return a.ti < b.ti
+end
+
+local function addItemToCell(self, item, cx, cy, cz)
+ self.cells[cz] = self.cells[cz] or {}
+ self.cells[cz][cy] = self.cells[cz][cy] or setmetatable({}, {__mode = 'v'})
+ if self.cells[cz][cy][cx] == nil then
+ self.cells[cz][cy][cx] = {
+ itemCount = 0,
+ x = cx,
+ y = cy,
+ z = cz,
+ items = setmetatable({}, {__mode = 'k'})
+ }
+ end
+
+ local cell = self.cells[cz][cy][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, cz)
+ if not self.cells[cz]
+ or not self.cells[cz][cy]
+ or not self.cells[cz][cy][cx]
+ or not self.cells[cz][cy][cx].items[item]
+ then
+ return false
+ end
+
+ local cell = self.cells[cz][cy][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 getDictItemsInCellCube(self, cx,cy,cz, cw,ch,cd)
+ local items_dict = {}
+
+ for z = cz, cz + cd - 1 do
+ local plane = self.cells[z]
+ if plane then
+ for y = cy, cy + ch - 1 do
+ local row = plane[y]
+ if row then
+ for x = cx, cx + cw - 1 do
+ local cell = row[x]
+ 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
+ end
+ end
+
+ return items_dict
+end
+
+local function getCellsTouchedBySegment(self, x1,y1,z1,x2,y2,z2)
+ local cells, cellsLen, visited = {}, 0, {}
+
+ grid_traverse(self.cellSize, x1,y1,z1,x2,y2,z2, function(cx, cy, cz)
+ local plane = self.cells[cz]
+ if not plane then
+ return
+ end
+
+ local row = plane[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,z1, x2,y2,z2, filter)
+ local cells, len = getCellsTouchedBySegment(self, x1,y1,z1,x2,y2,z2)
+ local cell, cube, x,y,z,w,h,d, 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
+ cube = self.cubes[item]
+ x, y, z, w, h, d = cube.x, cube.y, cube.z, cube.w, cube.h, cube.d
+
+ ti1, ti2 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1, x2,y2,z2, 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 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1, x2,y2,z2, -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:projectMove(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
+ local cols, len = {}, 0
+
+ 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 projected_cols, projected_len = self:project(item, x,y,z,w,h,d, goalX,goalY,goalZ, 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, goalZ, projected_cols, projected_len = response(
+ self,
+ col,
+ x, y, z, w, h, d,
+ goalX, goalY, goalZ,
+ visitedFilter
+ )
+ end
+
+ return goalX, goalY, goalZ, cols, len
+end
+
+function World:project(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
+ assertIsCube(x, y, z, w, h, d)
+
+ goalX = goalX or x
+ goalY = goalY or y
+ goalZ = goalZ or z
+ 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 cube of the whole movement. Conditional to building a queryPolygon method
+ local tx = min(goalX, x)
+ local ty = min(goalY, y)
+ local tz = min(goalZ, z)
+ local tx2 = max(goalX + w, x + w)
+ local ty2 = max(goalY + h, y + h)
+ local tz2 = max(goalZ + d, z + d)
+ local tw = tx2 - tx
+ local th = ty2 - ty
+ local td = tz2 - tz
+
+ local cx,cy,cz,cw,ch,cd = grid_toCellCube(self.cellSize, tx,ty,tz, tw,th,td)
+
+ local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz,cw,ch,cd)
+
+ for other,_ in pairs(dictItemsInCellCube) do
+ if not visited[other] then
+ visited[other] = true
+
+ local responseName = filter(item, other)
+ if responseName then
+ local ox,oy,oz,ow,oh,od = self:getCube(other)
+ local col = cube_detectCollision(x,y,z,w,h,d, ox,oy,oz,ow,oh,od, goalX, goalY, goalZ)
+
+ 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 _, plane in pairs(self.cells) do
+ for _, row in pairs(plane) do
+ for _,_ in pairs(row) do
+ count = count + 1
+ end
+ end
+ end
+
+ return count
+end
+
+function World:hasItem(item)
+ return not not self.cubes[item]
+end
+
+function World:getItems()
+ local items, len = {}, 0
+ for item,_ in pairs(self.cubes) do
+ len = len + 1
+ items[len] = item
+ end
+ return items, len
+end
+
+function World:countItems()
+ local len = 0
+ for _ in pairs(self.cubes) do len = len + 1 end
+ return len
+end
+
+function World:getCube(item)
+ local cube = self.cubes[item]
+ if not cube then
+ error('Item ' .. tostring(item) .. ' must be added to the world before getting its cube. Use world:add(item, x,y,z,w,h,d) to add it first.')
+ end
+
+ return cube.x, cube.y, cube.z, cube.w, cube.h, cube.d
+end
+
+function World:toWorld(cx, cy, cz)
+ return grid_toWorld(self.cellSize, cx, cy, cz)
+end
+
+function World:toCell(x,y,z)
+ return grid_toCell(self.cellSize, x, y, z)
+end
+
+
+-- Query methods
+
+function World:queryCube(x,y,z,w,h,d, filter)
+ assertIsCube(x,y,z,w,h,d)
+
+ local cx,cy,cz,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
+ local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz,cw,ch,cd)
+
+ local items, len = {}, 0
+
+ local cube
+ for item,_ in pairs(dictItemsInCellCube) do
+ cube = self.cubes[item]
+ if (not filter or filter(item))
+ and cube_isIntersecting(x,y,z,w,h,d, cube.x, cube.y, cube.z, cube.w, cube.h, cube.d)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:queryPoint(x,y,z, filter)
+ local cx,cy,cz = self:toCell(x,y,z)
+ local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz, 1,1,1)
+
+ local items, len = {}, 0
+
+ local cube
+ for item,_ in pairs(dictItemsInCellCube) do
+ cube = self.cubes[item]
+ if (not filter or filter(item))
+ and cube_containsPoint(cube.x, cube.y, cube.z, cube.w, cube.h, cube.d, x, y, z)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:querySegment(x1, y1, z1, x2, y2, z2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, z1, x2, y2, z2, filter)
+ local items = {}
+ for i = 1, len do
+ items[i] = itemInfo[i].item
+ end
+
+ return items, len
+end
+
+-- function World:querySegmentWithCoords(x1, y1, z1, x2, y2, z2, filter)
+-- local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, z1, x2, y2, z2, filter)
+-- local dx, dy, dz = x2 - x1, y2 - y1, z2 - z1
+-- 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,z,w,h,d)
+ local cube = self.cubes[item]
+ if cube then
+ error('Item ' .. tostring(item) .. ' added to the world twice.')
+ end
+ assertIsCube(x,y,z,w,h,d)
+
+ self.cubes[item] = {x=x,y=y,z=z,w=w,h=h,d=d}
+
+ local cl,ct,cs,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
+ for cz = cs, cs + cd - 1 do
+ for cy = ct, ct + ch - 1 do
+ for cx = cl, cl + cw - 1 do
+ addItemToCell(self, item, cx, cy, cz)
+ end
+ end
+ end
+
+ return item
+end
+
+function World:remove(item)
+ local x,y,z,w,h,d = self:getCube(item)
+
+ self.cubes[item] = nil
+ local cl,ct,cs,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
+ for cz = cs, cs + cd - 1 do
+ for cy = ct, ct + ch - 1 do
+ for cx = cl, cl + cw - 1 do
+ removeItemFromCell(self, item, cx, cy, cz)
+ end
+ end
+ end
+end
+
+function World:update(item, x2,y2,z2,w2,h2,d2)
+ local x1,y1,z1, w1,h1,d1 = self:getCube(item)
+ w2 = w2 or w1
+ h2 = h2 or h1
+ d2 = d2 or d1
+ assertIsCube(x2,y2,z2,w2,h2,d2)
+
+ if x1 == x2 and y1 == y2 and z1 == z2 and w1 == w2 and h1 == h2 and d1 == d2 then
+ return
+ end
+
+ local cl1,ct1,cs1,cw1,ch1,cd1 = grid_toCellCube(self.cellSize, x1,y1,z1, w1,h1,d1)
+ local cl2,ct2,cs2,cw2,ch2,cd2 = grid_toCellCube(self.cellSize, x2,y2,z2, w2,h2,d2)
+
+ if cl1 ~= cl2 or ct1 ~= ct2 or cs1 ~= cs2 or cw1 ~= cw2 or ch1 ~= ch2 or cd1 ~= cd2 then
+ local cr1 = cl1 + cw1 - 1
+ local cr2 = cl2 + cw2 - 1
+ local cb1 = ct1 + ch1 - 1
+ local cb2 = ct2 + ch2 - 1
+ local css1 = cs1 + cd1 - 1
+ local css2 = cs2 + cd2 - 1
+ local cyOut, czOut
+
+ for cz = cs1, css1 do
+ czOut = cz < cs2 or cz > css2
+ for cy = ct1, cb1 do
+ cyOut = cy < ct2 or cy > cb2
+ for cx = cl1, cr1 do
+ if czOut or cyOut or cx < cl2 or cx > cr2 then
+ removeItemFromCell(self, item, cx, cy, cz)
+ end
+ end
+ end
+ end
+
+ for cz = cs2, css2 do
+ czOut = cz < cs1 or cz > css1
+ for cy = ct2, cb2 do
+ cyOut = cy < ct1 or cy > cb1
+ for cx = cl2, cr2 do
+ if czOut or cyOut or cx < cl1 or cx > cr1 then
+ addItemToCell(self, item, cx, cy, cz)
+ end
+ end
+ end
+ end
+ end
+
+ local cube = self.cubes[item]
+ cube.x, cube.y, cube.z, cube.w, cube.h, cube.d = x2, y2, z2, w2, h2, d2
+end
+
+function World:move(item, goalX, goalY, goalZ, filter)
+ local actualX, actualY, actualZ, cols, len = self:check(item, goalX, goalY, goalZ, filter)
+
+ self:update(item, actualX, actualY, actualZ)
+
+ return actualX, actualY, actualZ, cols, len
+end
+
+function World:check(item, goalX, goalY, goalZ, filter)
+ local x,y,z,w,h,d = self:getCube(item)
+
+ return self:projectMove(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
+end
+
+
+-- Public library functions
+
+bump.newWorld = function(cellSize)
+ cellSize = cellSize or 64
+ assertIsPositiveNumber(cellSize, 'cellSize')
+ local world = setmetatable({
+ cellSize = cellSize,
+ cubes = {},
+ cells = {},
+ nonEmptyCells = {},
+ responses = {},
+ }, World_mt)
+
+ world:addResponse('touch', touch)
+ world:addResponse('cross', cross)
+ world:addResponse('slide', slide)
+ world:addResponse('bounce', bounce)
+
+ return world
+end
+
+bump.cube = {
+ getNearestCorner = cube_getNearestCorner,
+ getSegmentIntersectionIndices = cube_getSegmentIntersectionIndices,
+ getDiff = cube_getDiff,
+ containsPoint = cube_containsPoint,
+ isIntersecting = cube_isIntersecting,
+ getCubeDistance = cube_getCubeDistance,
+ detectCollision = cube_detectCollision
+}
+
+bump.responses = {
+ touch = touch,
+ cross = cross,
+ slide = slide,
+ bounce = bounce
+}
+
+return bump
diff --git a/imperium-porcorum.love/core/modules/world/libs/bump.lua b/imperium-porcorum.love/core/modules/world/libs/bump.lua
new file mode 100644
index 0000000..66d4cf1
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/bump.lua
@@ -0,0 +1,769 @@
+local bump = {
+ _VERSION = 'bump v3.1.7',
+ _URL = 'https://github.com/kikito/bump.lua',
+ _DESCRIPTION = 'A collision detection library for Lua',
+ _LICENSE = [[
+ MIT LICENSE
+ Copyright (c) 2014 Enrique García Cota
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ ]]
+}
+
+------------------------------------------
+-- Auxiliary functions
+------------------------------------------
+local DELTA = 1e-10 -- floating-point margin of error
+
+local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
+
+local function sign(x)
+ if x > 0 then return 1 end
+ if x == 0 then return 0 end
+ return -1
+end
+
+local function nearest(x, a, b)
+ if abs(a - x) < abs(b - x) then return a else return b end
+end
+
+local function assertType(desiredType, value, name)
+ if type(value) ~= desiredType then
+ error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
+ end
+end
+
+local function assertIsPositiveNumber(value, name)
+ if type(value) ~= 'number' or value <= 0 then
+ error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
+ end
+end
+
+local function assertIsRect(x,y,w,h)
+ assertType('number', x, 'x')
+ assertType('number', y, 'y')
+ assertIsPositiveNumber(w, 'w')
+ assertIsPositiveNumber(h, 'h')
+end
+
+local defaultFilter = function()
+ return 'slide'
+end
+
+------------------------------------------
+-- Rectangle functions
+------------------------------------------
+
+local function rect_getNearestCorner(x,y,w,h, px, py)
+ return nearest(px, x, x+w), nearest(py, y, y+h)
+end
+
+-- This is a generalized implementation of the liang-barsky algorithm, which also returns
+-- the normals of the sides where the segment intersects.
+-- Returns nil if the segment never touches the rect
+-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
+local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
+ ti1, ti2 = ti1 or 0, ti2 or 1
+ local dx, dy = x2-x1, y2-y1
+ local nx, ny
+ local nx1, ny1, nx2, ny2 = 0,0,0,0
+ local p, q, r
+
+ for side = 1,4 do
+ if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
+ elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
+ elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
+ else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
+ end
+
+ if p == 0 then
+ if q <= 0 then return nil end
+ else
+ r = q / p
+ if p < 0 then
+ if r > ti2 then return nil
+ elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
+ end
+ else -- p > 0
+ if r < ti1 then return nil
+ elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
+ end
+ end
+ end
+ end
+
+ return ti1,ti2, nx1,ny1, nx2,ny2
+end
+
+-- Calculates the minkowsky difference between 2 rects, which is another rect
+local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x2 - x1 - w1,
+ y2 - y1 - h1,
+ w1 + w2,
+ h1 + h2
+end
+
+local function rect_containsPoint(x,y,w,h, px,py)
+ return px - x > DELTA and py - y > DELTA and
+ x + w - px > DELTA and y + h - py > DELTA
+end
+
+local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x1 < x2+w2 and x2 < x1+w1 and
+ y1 < y2+h2 and y2 < y1+h1
+end
+
+local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
+ local dx = x1 - x2 + (w1 - w2)/2
+ local dy = y1 - y2 + (h1 - h2)/2
+ return dx*dx + dy*dy
+end
+
+local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
+ goalX = goalX or x1
+ goalY = goalY or y1
+
+ local dx, dy = goalX - x1, goalY - y1
+ local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+
+ local overlaps, ti, nx, ny
+
+ if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
+ local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
+ local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
+ ti = -wi * hi -- ti is the negative area of intersection
+ overlaps = true
+ else
+ local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
+
+ -- item tunnels into other
+ if ti1
+ and ti1 < 1
+ and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner
+ and (0 < ti1 + DELTA
+ or 0 == ti1 and ti2 > 0)
+ then
+ ti, nx, ny = ti1, nx1, ny1
+ overlaps = false
+ end
+ end
+
+ if not ti then return end
+
+ local tx, ty
+
+ if overlaps then
+ if dx == 0 and dy == 0 then
+ -- intersecting and not moving - use minimum displacement vector
+ local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
+ if abs(px) < abs(py) then py = 0 else px = 0 end
+ nx, ny = sign(px), sign(py)
+ tx, ty = x1 + px, y1 + py
+ else
+ -- intersecting and moving - move in the opposite direction
+ local ti1, _
+ ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
+ if not ti1 then return end
+ tx, ty = x1 + dx * ti1, y1 + dy * ti1
+ end
+ else -- tunnel
+ tx, ty = x1 + dx * ti, y1 + dy * ti
+ end
+
+ return {
+ overlaps = overlaps,
+ ti = ti,
+ move = {x = dx, y = dy},
+ normal = {x = nx, y = ny},
+ touch = {x = tx, y = ty},
+ itemRect = {x = x1, y = y1, w = w1, h = h1},
+ otherRect = {x = x2, y = y2, w = w2, h = h2}
+ }
+end
+
+------------------------------------------
+-- Grid functions
+------------------------------------------
+
+local function grid_toWorld(cellSize, cx, cy)
+ return (cx - 1)*cellSize, (cy-1)*cellSize
+end
+
+local function grid_toCell(cellSize, x, y)
+ return floor(x / cellSize) + 1, floor(y / cellSize) + 1
+end
+
+-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
+-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
+-- It has been modified to include both cells when the ray "touches a grid corner",
+-- and with a different exit condition
+
+local function grid_traverse_initStep(cellSize, ct, t1, t2)
+ local v = t2 - t1
+ if v > 0 then
+ return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
+ elseif v < 0 then
+ return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
+ else
+ return 0, math.huge, math.huge
+ end
+end
+
+local function grid_traverse(cellSize, x1,y1,x2,y2, f)
+ local cx1,cy1 = grid_toCell(cellSize, x1,y1)
+ local cx2,cy2 = grid_toCell(cellSize, x2,y2)
+ local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
+ local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
+ local cx,cy = cx1,cy1
+
+ f(cx, cy)
+
+ -- The default implementation had an infinite loop problem when
+ -- approaching the last cell in some occassions. We finish iterating
+ -- when we are *next* to the last cell
+ while abs(cx - cx2) + abs(cy - cy2) > 1 do
+ if tx < ty then
+ tx, cx = tx + dx, cx + stepX
+ f(cx, cy)
+ else
+ -- Addition: include both cells when going through corners
+ if tx == ty then f(cx + stepX, cy) end
+ ty, cy = ty + dy, cy + stepY
+ f(cx, cy)
+ end
+ end
+
+ -- If we have not arrived to the last cell, use it
+ if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
+
+end
+
+local function grid_toCellRect(cellSize, x,y,w,h)
+ local cx,cy = grid_toCell(cellSize, x, y)
+ local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
+ return cx, cy, cr - cx + 1, cb - cy + 1
+end
+
+------------------------------------------
+-- Responses
+------------------------------------------
+
+local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
+ return col.touch.x, col.touch.y, {}, 0
+end
+
+local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ if move.x ~= 0 or move.y ~= 0 then
+ if col.normal.x ~= 0 then
+ goalX = tch.x
+ else
+ goalY = tch.y
+ end
+ end
+
+ col.slide = {x = goalX, y = goalY}
+
+ x,y = tch.x, tch.y
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ local tx, ty = tch.x, tch.y
+
+ local bx, by = tx, ty
+
+ if move.x ~= 0 or move.y ~= 0 then
+ local bnx, bny = goalX - tx, goalY - ty
+ if col.normal.x == 0 then bny = -bny else bnx = -bnx end
+ bx, by = tx + bnx, ty + bny
+ end
+
+ col.bounce = {x = bx, y = by}
+ x,y = tch.x, tch.y
+ goalX, goalY = bx, by
+
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+------------------------------------------
+-- World
+------------------------------------------
+
+local World = {}
+local World_mt = {__index = World}
+
+-- Private functions and methods
+
+local function sortByWeight(a,b) return a.weight < b.weight end
+
+local function sortByTiAndDistance(a,b)
+ if a.ti == b.ti then
+ local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
+ local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
+ local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
+ return ad < bd
+ end
+ return a.ti < b.ti
+end
+
+local function addItemToCell(self, item, cx, cy)
+ self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
+ local row = self.rows[cy]
+ row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
+ local cell = row[cx]
+ self.nonEmptyCells[cell] = true
+ if not cell.items[item] then
+ cell.items[item] = true
+ cell.itemCount = cell.itemCount + 1
+ end
+end
+
+local function removeItemFromCell(self, item, cx, cy)
+ local row = self.rows[cy]
+ if not row or not row[cx] or not row[cx].items[item] then return false end
+
+ local cell = row[cx]
+ cell.items[item] = nil
+ cell.itemCount = cell.itemCount - 1
+ if cell.itemCount == 0 then
+ self.nonEmptyCells[cell] = nil
+ end
+ return true
+end
+
+local function getDictItemsInCellRect(self, cl,ct,cw,ch)
+ local items_dict = {}
+ for cy=ct,ct+ch-1 do
+ local row = self.rows[cy]
+ if row then
+ for cx=cl,cl+cw-1 do
+ local cell = row[cx]
+ if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
+ for item,_ in pairs(cell.items) do
+ items_dict[item] = true
+ end
+ end
+ end
+ end
+ end
+
+ return items_dict
+end
+
+local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
+
+ local cells, cellsLen, visited = {}, 0, {}
+
+ grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
+ local row = self.rows[cy]
+ if not row then return end
+ local cell = row[cx]
+ if not cell or visited[cell] then return end
+
+ visited[cell] = true
+ cellsLen = cellsLen + 1
+ cells[cellsLen] = cell
+ end)
+
+ return cells, cellsLen
+end
+
+local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
+ local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
+ local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
+ local visited, itemInfo, itemInfoLen = {},{},0
+ for i=1,len do
+ cell = cells[i]
+ for item in pairs(cell.items) do
+ if not visited[item] then
+ visited[item] = true
+ if (not filter or filter(item)) then
+ rect = self.rects[item]
+ l,t,w,h = rect.x,rect.y,rect.w,rect.h
+
+ ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
+ if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
+ -- the sorting is according to the t of an infinite line, not the segment
+ tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
+ itemInfoLen = itemInfoLen + 1
+ itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
+ end
+ end
+ end
+ end
+ end
+ table.sort(itemInfo, sortByWeight)
+ return itemInfo, itemInfoLen
+end
+
+local function getResponseByName(self, name)
+ local response = self.responses[name]
+ if not response then
+ error(('Unknown collision type: %s (%s)'):format(name, type(name)))
+ end
+ return response
+end
+
+
+-- Misc Public Methods
+
+function World:addResponse(name, response)
+ self.responses[name] = response
+end
+
+function World:project(item, x,y,w,h, goalX, goalY, filter)
+ assertIsRect(x,y,w,h)
+
+ goalX = goalX or x
+ goalY = goalY or y
+ filter = filter or defaultFilter
+
+ local collisions, len = {}, 0
+
+ local visited = {}
+ if item ~= nil then visited[item] = true end
+
+ -- This could probably be done with less cells using a polygon raster over the cells instead of a
+ -- bounding rect of the whole movement. Conditional to building a queryPolygon method
+ local tl, tt = min(goalX, x), min(goalY, y)
+ local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
+ local tw, th = tr-tl, tb-tt
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
+
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ for other,_ in pairs(dictItemsInCellRect) do
+ if not visited[other] then
+ visited[other] = true
+
+ local responseName = filter(item, other)
+ if responseName then
+ local ox,oy,ow,oh = self:getRect(other)
+ local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
+
+ if col then
+ col.other = other
+ col.item = item
+ col.type = responseName
+
+ len = len + 1
+ collisions[len] = col
+ end
+ end
+ end
+ end
+
+ table.sort(collisions, sortByTiAndDistance)
+
+ return collisions, len
+end
+
+function World:countCells()
+ local count = 0
+ for _,row in pairs(self.rows) do
+ for _,_ in pairs(row) do
+ count = count + 1
+ end
+ end
+ return count
+end
+
+function World:hasItem(item)
+ return not not self.rects[item]
+end
+
+function World:getItems()
+ local items, len = {}, 0
+ for item,_ in pairs(self.rects) do
+ len = len + 1
+ items[len] = item
+ end
+ return items, len
+end
+
+function World:countItems()
+ local len = 0
+ for _ in pairs(self.rects) do len = len + 1 end
+ return len
+end
+
+function World:getRect(item)
+ local rect = self.rects[item]
+ if not rect then
+ error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
+ end
+ return rect.x, rect.y, rect.w, rect.h
+end
+
+function World:toWorld(cx, cy)
+ return grid_toWorld(self.cellSize, cx, cy)
+end
+
+function World:toCell(x,y)
+ return grid_toCell(self.cellSize, x, y)
+end
+
+
+--- Query methods
+
+function World:queryRect(x,y,w,h, filter)
+
+ assertIsRect(x,y,w,h)
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:queryPoint(x,y, filter)
+ local cx,cy = self:toCell(x,y)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:querySegment(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local items = {}
+ for i=1, len do
+ items[i] = itemInfo[i].item
+ end
+ return items, len
+end
+
+function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local dx, dy = x2-x1, y2-y1
+ local info, ti1, ti2
+ for i=1, len do
+ info = itemInfo[i]
+ ti1 = info.ti1
+ ti2 = info.ti2
+
+ info.weight = nil
+ info.x1 = x1 + dx * ti1
+ info.y1 = y1 + dy * ti1
+ info.x2 = x1 + dx * ti2
+ info.y2 = y1 + dy * ti2
+ end
+ return itemInfo, len
+end
+
+
+--- Main methods
+
+function World:add(item, x,y,w,h)
+ local rect = self.rects[item]
+ if rect then
+ error('Item ' .. tostring(item) .. ' added to the world twice.')
+ end
+ assertIsRect(x,y,w,h)
+
+ self.rects[item] = {x=x,y=y,w=w,h=h}
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+
+ return item
+end
+
+function World:remove(item)
+ local x,y,w,h = self:getRect(item)
+
+ self.rects[item] = nil
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+end
+
+function World:update(item, x2,y2,w2,h2)
+ local x1,y1,w1,h1 = self:getRect(item)
+ w2,h2 = w2 or w1, h2 or h1
+ assertIsRect(x2,y2,w2,h2)
+
+ if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
+
+ local cellSize = self.cellSize
+ local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
+ local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
+
+ if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
+
+ local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
+ local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
+ local cyOut
+
+ for cy = ct1, cb1 do
+ cyOut = cy < ct2 or cy > cb2
+ for cx = cl1, cr1 do
+ if cyOut or cx < cl2 or cx > cr2 then
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ for cy = ct2, cb2 do
+ cyOut = cy < ct1 or cy > cb1
+ for cx = cl2, cr2 do
+ if cyOut or cx < cl1 or cx > cr1 then
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ end
+
+ local rect = self.rects[item]
+ rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
+
+ end
+end
+
+function World:move(item, goalX, goalY, filter)
+ local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
+
+ self:update(item, actualX, actualY)
+
+ return actualX, actualY, cols, len
+end
+
+function World:check(item, goalX, goalY, filter)
+ filter = filter or defaultFilter
+
+ local visited = {[item] = true}
+ local visitedFilter = function(itm, other)
+ if visited[other] then return false end
+ return filter(itm, other)
+ end
+
+ local cols, len = {}, 0
+
+ local x,y,w,h = self:getRect(item)
+
+ local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
+
+ while projected_len > 0 do
+ local col = projected_cols[1]
+ len = len + 1
+ cols[len] = col
+
+ visited[col.other] = true
+
+ local response = getResponseByName(self, col.type)
+
+ goalX, goalY, projected_cols, projected_len = response(
+ self,
+ col,
+ x, y, w, h,
+ goalX, goalY,
+ visitedFilter
+ )
+ end
+
+ return goalX, goalY, cols, len
+end
+
+
+-- Public library functions
+
+bump.newWorld = function(cellSize)
+ cellSize = cellSize or 64
+ assertIsPositiveNumber(cellSize, 'cellSize')
+ local world = setmetatable({
+ cellSize = cellSize,
+ rects = {},
+ rows = {},
+ nonEmptyCells = {},
+ responses = {}
+ }, World_mt)
+
+ world:addResponse('touch', touch)
+ world:addResponse('cross', cross)
+ world:addResponse('slide', slide)
+ world:addResponse('bounce', bounce)
+
+ return world
+end
+
+bump.rect = {
+ getNearestCorner = rect_getNearestCorner,
+ getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
+ getDiff = rect_getDiff,
+ containsPoint = rect_containsPoint,
+ isIntersecting = rect_isIntersecting,
+ getSquareDistance = rect_getSquareDistance,
+ detectCollision = rect_detectCollision
+}
+
+bump.responses = {
+ touch = touch,
+ cross = cross,
+ slide = slide,
+ bounce = bounce
+}
+
+return bump
diff --git a/imperium-porcorum.love/core/modules/world/libs/hump/camera.lua b/imperium-porcorum.love/core/modules/world/libs/hump/camera.lua
new file mode 100644
index 0000000..cb86a79
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/hump/camera.lua
@@ -0,0 +1,216 @@
+--[[
+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})
diff --git a/imperium-porcorum.love/core/modules/world/libs/sti/graphics.lua b/imperium-porcorum.love/core/modules/world/libs/sti/graphics.lua
new file mode 100644
index 0000000..1d73379
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/sti/graphics.lua
@@ -0,0 +1,128 @@
+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
diff --git a/imperium-porcorum.love/core/modules/world/libs/sti/init.lua b/imperium-porcorum.love/core/modules/world/libs/sti/init.lua
new file mode 100644
index 0000000..de1bd16
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/sti/init.lua
@@ -0,0 +1,1485 @@
+--- Simple and fast Tiled map loader and renderer.
+-- @module sti
+-- @author Landon Manning
+-- @copyright 2016
+-- @license MIT/X11
+
+local STI = {
+ _LICENSE = "MIT/X11",
+ _URL = "https://github.com/karai17/Simple-Tiled-Implementation",
+ _VERSION = "0.18.2.1",
+ _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.",
+ cache = {}
+}
+STI.__index = STI
+
+local cwd = (...):gsub('%.init$', '') .. "."
+local utils = require(cwd .. "utils")
+local ceil = math.ceil
+local floor = math.floor
+local lg = require(cwd .. "graphics")
+local Map = {}
+Map.__index = Map
+
+local function new(map, plugins, ox, oy)
+ local dir = ""
+
+ if type(map) == "table" then
+ map = setmetatable(map, Map)
+ else
+ -- Check for valid map type
+ local ext = map:sub(-4, -1)
+ assert(ext == ".lua", string.format(
+ "Invalid file type: %s. File must be of type: lua.",
+ ext
+ ))
+
+ -- Get directory of map
+ dir = map:reverse():find("[/\\]") or ""
+ if dir ~= "" then
+ dir = map:sub(1, 1 + (#map - dir))
+ end
+
+ -- Load map
+ map = setmetatable(assert(love.filesystem.load(map))(), Map)
+ end
+
+ map:init(dir, plugins, ox, oy)
+
+ return map
+end
+
+--- Instance a new map.
+-- @param map Path to the map file or the map table itself
+-- @param plugins A list of plugins to load
+-- @param ox Offset of map on the X axis (in pixels)
+-- @param oy Offset of map on the Y axis (in pixels)
+-- @return table The loaded Map
+function STI.__call(_, map, plugins, ox, oy)
+ return new(map, plugins, ox, oy)
+end
+
+--- Flush image cache.
+function STI:flush()
+ self.cache = {}
+end
+
+--- Map object
+
+--- Instance a new map
+-- @param path Path to the map file
+-- @param plugins A list of plugins to load
+-- @param ox Offset of map on the X axis (in pixels)
+-- @param oy Offset of map on the Y axis (in pixels)
+function Map:init(path, plugins, ox, oy)
+ if type(plugins) == "table" then
+ self:loadPlugins(plugins)
+ end
+
+ self:resize()
+ self.objects = {}
+ self.tiles = {}
+ self.tileInstances = {}
+ self.drawRange = {
+ sx = 1,
+ sy = 1,
+ ex = self.width,
+ ey = self.height,
+ }
+ self.offsetx = ox or 0
+ self.offsety = oy or 0
+
+ self.freeBatchSprites = {}
+ setmetatable(self.freeBatchSprites, { __mode = 'k' })
+
+ -- Set tiles, images
+ local gid = 1
+ for i, tileset in ipairs(self.tilesets) do
+ assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.")
+
+ -- Cache images
+ if lg.isCreated then
+ local formatted_path = utils.format_path(path .. tileset.image)
+
+ if not STI.cache[formatted_path] then
+ utils.fix_transparent_color(tileset, formatted_path)
+ utils.cache_image(STI, formatted_path, tileset.image)
+ else
+ tileset.image = STI.cache[formatted_path]
+ end
+ end
+
+ gid = self:setTiles(i, tileset, gid)
+ end
+
+ -- Set layers
+ for _, layer in ipairs(self.layers) do
+ self:setLayer(layer, path)
+ end
+end
+
+--- Load plugins
+-- @param plugins A list of plugins to load
+function Map:loadPlugins(plugins)
+ for _, plugin in ipairs(plugins) do
+ local pluginModulePath = cwd .. 'plugins.' .. plugin
+ local ok, pluginModule = pcall(require, pluginModulePath)
+ if ok then
+ for k, func in pairs(pluginModule) do
+ if not self[k] then
+ self[k] = func
+ end
+ end
+ end
+ end
+end
+
+--- Create Tiles
+-- @param index Index of the Tileset
+-- @param tileset Tileset data
+-- @param gid First Global ID in Tileset
+-- @return number Next Tileset's first Global ID
+function Map:setTiles(index, tileset, gid)
+ local quad = lg.newQuad
+ local imageW = tileset.imagewidth
+ local imageH = tileset.imageheight
+ local tileW = tileset.tilewidth
+ local tileH = tileset.tileheight
+ local margin = tileset.margin
+ local spacing = tileset.spacing
+ local w = utils.get_tiles(imageW, tileW, margin, spacing)
+ local h = utils.get_tiles(imageH, tileH, margin, spacing)
+
+ for y = 1, h do
+ for x = 1, w do
+ local id = gid - tileset.firstgid
+ local quadX = (x - 1) * tileW + margin + (x - 1) * spacing
+ local quadY = (y - 1) * tileH + margin + (y - 1) * spacing
+ local properties, terrain, animation, objectGroup
+
+ for _, tile in pairs(tileset.tiles) do
+ if tile.id == id then
+ properties = tile.properties
+ animation = tile.animation
+ objectGroup = tile.objectGroup
+
+ if tile.terrain then
+ terrain = {}
+
+ for i = 1, #tile.terrain do
+ terrain[i] = tileset.terrains[tile.terrain[i] + 1]
+ end
+ end
+ end
+ end
+
+ local tile = {
+ id = id,
+ gid = gid,
+ tileset = index,
+ quad = quad(
+ quadX, quadY,
+ tileW, tileH,
+ imageW, imageH
+ ),
+ properties = properties or {},
+ terrain = terrain,
+ animation = animation,
+ objectGroup = objectGroup,
+ frame = 1,
+ time = 0,
+ width = tileW,
+ height = tileH,
+ sx = 1,
+ sy = 1,
+ r = 0,
+ offset = tileset.tileoffset,
+ }
+
+ self.tiles[gid] = tile
+ gid = gid + 1
+ end
+ end
+
+ return gid
+end
+
+--- Create Layers
+-- @param layer Layer data
+-- @param path (Optional) Path to an Image Layer's image
+function Map:setLayer(layer, path)
+ if layer.encoding then
+ if layer.encoding == "base64" then
+ assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".")
+ local fd = love.data.decode("string", "base64", layer.data)
+
+ if not layer.compression then
+ layer.data = utils.get_decompressed_data(fd)
+ else
+ assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".")
+
+ if layer.compression == "zlib" then
+ local data = love.data.decompress("string", "zlib", fd)
+ layer.data = utils.get_decompressed_data(data)
+ end
+
+ if layer.compression == "gzip" then
+ local data = love.data.decompress("string", "gzip", fd)
+ layer.data = utils.get_decompressed_data(data)
+ end
+ end
+ end
+ end
+
+ layer.x = (layer.x or 0) + layer.offsetx + self.offsetx
+ layer.y = (layer.y or 0) + layer.offsety + self.offsety
+ layer.update = function() end
+
+ if layer.type == "tilelayer" then
+ self:setTileData(layer)
+ self:setSpriteBatches(layer)
+ layer.draw = function() self:drawTileLayer(layer) end
+ elseif layer.type == "objectgroup" then
+ self:setObjectData(layer)
+ self:setObjectCoordinates(layer)
+ self:setObjectSpriteBatches(layer)
+ layer.draw = function() self:drawObjectLayer(layer) end
+ elseif layer.type == "imagelayer" then
+ layer.draw = function() self:drawImageLayer(layer) end
+
+ if layer.image ~= "" then
+ local formatted_path = utils.format_path(path .. layer.image)
+ if not STI.cache[formatted_path] then
+ utils.cache_image(STI, formatted_path)
+ end
+
+ layer.image = STI.cache[formatted_path]
+ layer.width = layer.image:getWidth()
+ layer.height = layer.image:getHeight()
+ end
+ end
+
+ self.layers[layer.name] = layer
+end
+
+--- Add Tiles to Tile Layer
+-- @param layer The Tile Layer
+function Map:setTileData(layer)
+ local i = 1
+ local map = {}
+
+ for y = 1, layer.height do
+ map[y] = {}
+ for x = 1, layer.width do
+ local gid = layer.data[i]
+
+ if gid > 0 then
+ map[y][x] = self.tiles[gid] or self:setFlippedGID(gid)
+ end
+
+ i = i + 1
+ end
+ end
+
+ layer.data = map
+end
+
+--- Add Objects to Layer
+-- @param layer The Object Layer
+function Map:setObjectData(layer)
+ for _, object in ipairs(layer.objects) do
+ object.layer = layer
+ self.objects[object.id] = object
+ end
+end
+
+--- Correct position and orientation of Objects in an Object Layer
+-- @param layer The Object Layer
+function Map:setObjectCoordinates(layer)
+ for _, object in ipairs(layer.objects) do
+ local x = layer.x + object.x
+ local y = layer.y + object.y
+ local w = object.width
+ local h = object.height
+ local r = object.rotation
+ local cos = math.cos(math.rad(r))
+ local sin = math.sin(math.rad(r))
+
+ if object.shape == "rectangle" and not object.gid then
+ object.rectangle = {}
+
+ local vertices = {
+ { x=x, y=y },
+ { x=x + w, y=y },
+ { x=x + w, y=y + h },
+ { x=x, y=y + h },
+ }
+
+ for _, vertex in ipairs(vertices) do
+ vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
+ table.insert(object.rectangle, { x = vertex.x, y = vertex.y })
+ end
+ elseif object.shape == "ellipse" then
+ object.ellipse = {}
+ local vertices = utils.convert_ellipse_to_polygon(x, y, w, h)
+
+ for _, vertex in ipairs(vertices) do
+ vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
+ table.insert(object.ellipse, { x = vertex.x, y = vertex.y })
+ end
+ elseif object.shape == "polygon" then
+ for _, vertex in ipairs(object.polygon) do
+ vertex.x = vertex.x + x
+ vertex.y = vertex.y + y
+ vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
+ end
+ elseif object.shape == "polyline" then
+ for _, vertex in ipairs(object.polyline) do
+ vertex.x = vertex.x + x
+ vertex.y = vertex.y + y
+ vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
+ end
+ end
+ end
+end
+
+--- Convert tile location to tile instance location
+-- @param layer Tile layer
+-- @param tile Tile
+-- @param x Tile location on X axis (in tiles)
+-- @param y Tile location on Y axis (in tiles)
+-- @return number Tile instance location on X axis (in pixels)
+-- @return number Tile instance location on Y axis (in pixels)
+function Map:getLayerTilePosition(layer, tile, x, y)
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local tileX, tileY
+
+ if self.orientation == "orthogonal" then
+ local tileset = self.tilesets[tile.tileset]
+ tileX = (x - 1) * tileW + tile.offset.x
+ tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight
+ tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH)
+ elseif self.orientation == "isometric" then
+ tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2
+ tileY = (x + y - 2) * (tileH / 2) + tile.offset.y
+ else
+ local sideLen = self.hexsidelength or 0
+ if self.staggeraxis == "y" then
+ if self.staggerindex == "odd" then
+ if y % 2 == 0 then
+ tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
+ else
+ tileX = (x - 1) * tileW + tile.offset.x
+ end
+ else
+ if y % 2 == 0 then
+ tileX = (x - 1) * tileW + tile.offset.x
+ else
+ tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
+ end
+ end
+
+ local rowH = tileH - (tileH - sideLen) / 2
+ tileY = (y - 1) * rowH + tile.offset.y
+ else
+ if self.staggerindex == "odd" then
+ if x % 2 == 0 then
+ tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
+ else
+ tileY = (y - 1) * tileH + tile.offset.y
+ end
+ else
+ if x % 2 == 0 then
+ tileY = (y - 1) * tileH + tile.offset.y
+ else
+ tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
+ end
+ end
+
+ local colW = tileW - (tileW - sideLen) / 2
+ tileX = (x - 1) * colW + tile.offset.x
+ end
+ end
+
+ return tileX, tileY
+end
+
+--- Place new tile instance
+-- @param layer Tile layer
+-- @param tile Tile
+-- @param number Tile location on X axis (in tiles)
+-- @param number Tile location on Y axis (in tiles)
+function Map:addNewLayerTile(layer, tile, x, y)
+ local tileset = tile.tileset
+ local image = self.tilesets[tile.tileset].image
+
+ layer.batches[tileset] = layer.batches[tileset]
+ or lg.newSpriteBatch(image, layer.width * layer.height)
+
+ local batch = layer.batches[tileset]
+ local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
+
+ local tab = {
+ layer = layer,
+ gid = tile.gid,
+ x = tileX,
+ y = tileY,
+ r = tile.r,
+ oy = 0
+ }
+
+ if batch then
+ tab.batch = batch
+ tab.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy)
+ end
+
+ self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
+ table.insert(self.tileInstances[tile.gid], tab)
+end
+
+--- Batch Tiles in Tile Layer for improved draw speed
+-- @param layer The Tile Layer
+function Map:setSpriteBatches(layer)
+ layer.batches = {}
+
+ if self.orientation == "orthogonal" or self.orientation == "isometric" then
+ local startX = 1
+ local startY = 1
+ local endX = layer.width
+ local endY = layer.height
+ local incrementX = 1
+ local incrementY = 1
+
+ -- Determine order to add tiles to sprite batch
+ -- Defaults to right-down
+ if self.renderorder == "right-up" then
+ startX, endX, incrementX = startX, endX, 1
+ startY, endY, incrementY = endY, startY, -1
+ elseif self.renderorder == "left-down" then
+ startX, endX, incrementX = endX, startX, -1
+ startY, endY, incrementY = startY, endY, 1
+ elseif self.renderorder == "left-up" then
+ startX, endX, incrementX = endX, startX, -1
+ startY, endY, incrementY = endY, startY, -1
+ end
+
+ for y = startY, endY, incrementY do
+ for x = startX, endX, incrementX do
+ local tile = layer.data[y][x]
+
+ if tile then
+ self:addNewLayerTile(layer, tile, x, y)
+ end
+ end
+ end
+ else
+ local sideLen = self.hexsidelength or 0
+
+ if self.staggeraxis == "y" then
+ for y = 1, layer.height do
+ for x = 1, layer.width do
+ local tile = layer.data[y][x]
+
+ if tile then
+ self:addNewLayerTile(layer, tile, x, y)
+ end
+ end
+ end
+ else
+ local i = 0
+ local _x
+
+ if self.staggerindex == "odd" then
+ _x = 1
+ else
+ _x = 2
+ end
+
+ while i < layer.width * layer.height do
+ for _y = 1, layer.height + 0.5, 0.5 do
+ local y = floor(_y)
+
+ for x = _x, layer.width, 2 do
+ i = i + 1
+ local tile = layer.data[y][x]
+
+ if tile then
+ self:addNewLayerTile(layer, tile, x, y)
+ end
+ end
+
+ if _x == 1 then
+ _x = 2
+ else
+ _x = 1
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Batch Tiles in Object Layer for improved draw speed
+-- @param layer The Object Layer
+function Map:setObjectSpriteBatches(layer)
+ local newBatch = lg.newSpriteBatch
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local batches = {}
+
+ if layer.draworder == "topdown" then
+ table.sort(layer.objects, function(a, b)
+ return a.y + a.height < b.y + b.height
+ end)
+ end
+
+ for _, object in ipairs(layer.objects) do
+ if object.gid then
+ local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid)
+ local tileset = tile.tileset
+ local image = self.tilesets[tile.tileset].image
+
+ batches[tileset] = batches[tileset] or newBatch(image)
+
+ local sx = object.width / tile.width
+ local sy = object.height / tile.height
+
+ local batch = batches[tileset]
+ local tileX = object.x + tile.offset.x
+ local tileY = object.y + tile.offset.y - tile.height * sy
+ local tileR = math.rad(object.rotation)
+ local oy = 0
+
+ -- Compensation for scale/rotation shift
+ if tile.sx == 1 and tile.sy == 1 then
+ if tileR ~= 0 then
+ tileY = tileY + tileH
+ oy = tileH
+ end
+ else
+ if tile.sx < 0 then tileX = tileX + tileW end
+ if tile.sy < 0 then tileY = tileY + tileH end
+ if tileR > 0 then tileX = tileX + tileW end
+ if tileR < 0 then tileY = tileY + tileH end
+ end
+
+ local tab = {
+ layer = layer,
+ gid = tile.gid,
+ x = tileX,
+ y = tileY,
+ r = tileR,
+ oy = oy
+ }
+
+ if batch then
+ tab.batch = batch
+ tab.id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, 0, oy)
+ end
+
+ self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
+ table.insert(self.tileInstances[tile.gid], tab)
+ end
+ end
+
+ layer.batches = batches
+end
+
+--- Create a Custom Layer to place userdata in (such as player sprites)
+-- @param name Name of Custom Layer
+-- @param index Draw order within Layer stack
+-- @return table Custom Layer
+function Map:addCustomLayer(name, index)
+ index = index or #self.layers + 1
+ local layer = {
+ type = "customlayer",
+ name = name,
+ visible = true,
+ opacity = 1,
+ properties = {},
+ }
+
+ function layer.draw() end
+ function layer.update() end
+
+ table.insert(self.layers, index, layer)
+ self.layers[name] = self.layers[index]
+
+ return layer
+end
+
+--- Convert another Layer into a Custom Layer
+-- @param index Index or name of Layer to convert
+-- @return table Custom Layer
+function Map:convertToCustomLayer(index)
+ local layer = assert(self.layers[index], "Layer not found: " .. index)
+
+ layer.type = "customlayer"
+ layer.x = nil
+ layer.y = nil
+ layer.width = nil
+ layer.height = nil
+ layer.encoding = nil
+ layer.data = nil
+ layer.objects = nil
+ layer.image = nil
+
+ function layer.draw() end
+ function layer.update() end
+
+ return layer
+end
+
+--- Remove a Layer from the Layer stack
+-- @param index Index or name of Layer to convert
+function Map:removeLayer(index)
+ local layer = assert(self.layers[index], "Layer not found: " .. index)
+
+ if type(index) == "string" then
+ for i, l in ipairs(self.layers) do
+ if l.name == index then
+ table.remove(self.layers, i)
+ self.layers[index] = nil
+ break
+ end
+ end
+ else
+ local name = self.layers[index].name
+ table.remove(self.layers, index)
+ self.layers[name] = nil
+ end
+
+ -- Remove tile instances
+ if layer.batches then
+ for _, batch in pairs(layer.batches) do
+ self.freeBatchSprites[batch] = nil
+ end
+
+ for _, tiles in pairs(self.tileInstances) do
+ for i = #tiles, 1, -1 do
+ local tile = tiles[i]
+ if tile.layer == layer then
+ table.remove(tiles, i)
+ end
+ end
+ end
+ end
+
+ -- Remove objects
+ if layer.objects then
+ for i, object in pairs(self.objects) do
+ if object.layer == layer then
+ self.objects[i] = nil
+ end
+ end
+ end
+end
+
+--- Animate Tiles and update every Layer
+-- @param dt Delta Time
+function Map:update(dt)
+ for _, tile in pairs(self.tiles) do
+ local update = false
+
+ if tile.animation then
+ tile.time = tile.time + dt * 1000
+
+ while tile.time > tonumber(tile.animation[tile.frame].duration) do
+ update = true
+ tile.time = tile.time - tonumber(tile.animation[tile.frame].duration)
+ tile.frame = tile.frame + 1
+
+ if tile.frame > #tile.animation then tile.frame = 1 end
+ end
+
+ if update and self.tileInstances[tile.gid] then
+ for _, j in pairs(self.tileInstances[tile.gid]) do
+ local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid]
+ j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy)
+ end
+ end
+ end
+ end
+
+ for _, layer in ipairs(self.layers) do
+ layer:update(dt)
+ end
+end
+
+--- Draw every Layer
+-- @param tx Translate on X
+-- @param ty Translate on Y
+-- @param sx Scale on X
+-- @param sy Scale on Y
+function Map:draw(tx, ty, sx, sy)
+ local current_canvas = lg.getCanvas()
+ lg.setCanvas(self.canvas)
+ lg.clear()
+
+ -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues
+ -- Map is translated to correct position so the right section is drawn
+ lg.push()
+ lg.origin()
+ lg.translate(math.floor(tx or 0), math.floor(ty or 0))
+
+ for _, layer in ipairs(self.layers) do
+ if layer.visible and layer.opacity > 0 then
+ self:drawLayer(layer)
+ end
+ end
+
+ lg.pop()
+
+ -- Draw canvas at 0,0; this fixes scissoring issues
+ -- Map is scaled to correct scale so the right section is shown
+ lg.push()
+ lg.origin()
+ lg.scale(sx or 1, sy or sx or 1)
+
+ lg.setCanvas(current_canvas)
+ lg.draw(self.canvas)
+
+ lg.pop()
+end
+
+--- Draw an individual Layer
+-- @param layer The Layer to draw
+function Map.drawLayer(_, layer)
+ local r,g,b,a = lg.getColor()
+ lg.setColor(r, g, b, a * layer.opacity)
+ layer:draw()
+ lg.setColor(r,g,b,a)
+end
+
+--- Default draw function for Tile Layers
+-- @param layer The Tile Layer to draw
+function Map:drawTileLayer(layer)
+ if type(layer) == "string" or type(layer) == "number" then
+ layer = self.layers[layer]
+ end
+
+ assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer")
+
+ for _, batch in pairs(layer.batches) do
+ lg.draw(batch, floor(layer.x), floor(layer.y))
+ end
+end
+
+--- Default draw function for Object Layers
+-- @param layer The Object Layer to draw
+function Map:drawObjectLayer(layer)
+ if type(layer) == "string" or type(layer) == "number" then
+ layer = self.layers[layer]
+ end
+
+ assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup")
+
+ local line = { 160, 160, 160, 255 * layer.opacity }
+ local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 }
+ local r,g,b,a = lg.getColor()
+ local reset = { r, g, b, a * layer.opacity }
+
+ local function sortVertices(obj)
+ local vertex = {}
+
+ for _, v in ipairs(obj) do
+ table.insert(vertex, v.x)
+ table.insert(vertex, v.y)
+ end
+
+ return vertex
+ end
+
+ local function drawShape(obj, shape)
+ local vertex = sortVertices(obj)
+
+ if shape == "polyline" then
+ lg.setColor(line)
+ lg.line(vertex)
+ return
+ elseif shape == "polygon" then
+ lg.setColor(fill)
+ if not love.math.isConvex(vertex) then
+ local triangles = love.math.triangulate(vertex)
+ for _, triangle in ipairs(triangles) do
+ lg.polygon("fill", triangle)
+ end
+ else
+ lg.polygon("fill", vertex)
+ end
+ else
+ lg.setColor(fill)
+ lg.polygon("fill", vertex)
+ end
+
+ lg.setColor(line)
+ lg.polygon("line", vertex)
+ end
+
+ for _, object in ipairs(layer.objects) do
+ if object.shape == "rectangle" and not object.gid then
+ drawShape(object.rectangle, "rectangle")
+ elseif object.shape == "ellipse" then
+ drawShape(object.ellipse, "ellipse")
+ elseif object.shape == "polygon" then
+ drawShape(object.polygon, "polygon")
+ elseif object.shape == "polyline" then
+ drawShape(object.polyline, "polyline")
+ end
+ end
+
+ lg.setColor(reset)
+ for _, batch in pairs(layer.batches) do
+ lg.draw(batch, 0, 0)
+ end
+ lg.setColor(r,g,b,a)
+end
+
+--- Default draw function for Image Layers
+-- @param layer The Image Layer to draw
+function Map:drawImageLayer(layer)
+ if type(layer) == "string" or type(layer) == "number" then
+ layer = self.layers[layer]
+ end
+
+ assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer")
+
+ if layer.image ~= "" then
+ lg.draw(layer.image, layer.x, layer.y)
+ end
+end
+
+--- Resize the drawable area of the Map
+-- @param w The new width of the drawable area (in pixels)
+-- @param h The new Height of the drawable area (in pixels)
+function Map:resize(w, h)
+ if lg.isCreated then
+ w = w or lg.getWidth()
+ h = h or lg.getHeight()
+
+ self.canvas = lg.newCanvas(w, h)
+ self.canvas:setFilter("nearest", "nearest")
+ end
+end
+
+--- Create flipped or rotated Tiles based on bitop flags
+-- @param gid The flagged Global ID
+-- @return table Flipped Tile
+function Map:setFlippedGID(gid)
+ local bit31 = 2147483648
+ local bit30 = 1073741824
+ local bit29 = 536870912
+ local flipX = false
+ local flipY = false
+ local flipD = false
+ local realgid = gid
+
+ if realgid >= bit31 then
+ realgid = realgid - bit31
+ flipX = not flipX
+ end
+
+ if realgid >= bit30 then
+ realgid = realgid - bit30
+ flipY = not flipY
+ end
+
+ if realgid >= bit29 then
+ realgid = realgid - bit29
+ flipD = not flipD
+ end
+
+ local tile = self.tiles[realgid]
+ local data = {
+ id = tile.id,
+ gid = gid,
+ tileset = tile.tileset,
+ frame = tile.frame,
+ time = tile.time,
+ width = tile.width,
+ height = tile.height,
+ offset = tile.offset,
+ quad = tile.quad,
+ properties = tile.properties,
+ terrain = tile.terrain,
+ animation = tile.animation,
+ sx = tile.sx,
+ sy = tile.sy,
+ r = tile.r,
+ }
+
+ if flipX then
+ if flipY and flipD then
+ data.r = math.rad(-90)
+ data.sy = -1
+ elseif flipY then
+ data.sx = -1
+ data.sy = -1
+ elseif flipD then
+ data.r = math.rad(90)
+ else
+ data.sx = -1
+ end
+ elseif flipY then
+ if flipD then
+ data.r = math.rad(-90)
+ else
+ data.sy = -1
+ end
+ elseif flipD then
+ data.r = math.rad(90)
+ data.sy = -1
+ end
+
+ self.tiles[gid] = data
+
+ return self.tiles[gid]
+end
+
+--- Get custom properties from Layer
+-- @param layer The Layer
+-- @return table List of properties
+function Map:getLayerProperties(layer)
+ local l = self.layers[layer]
+
+ if not l then
+ return {}
+ end
+
+ return l.properties
+end
+
+--- Get custom properties from Tile
+-- @param layer The Layer that the Tile belongs to
+-- @param x The X axis location of the Tile (in tiles)
+-- @param y The Y axis location of the Tile (in tiles)
+-- @return table List of properties
+function Map:getTileProperties(layer, x, y)
+ local tile = self.layers[layer].data[y][x]
+
+ if not tile then
+ return {}
+ end
+
+ return tile.properties
+end
+
+--- Get custom properties from Object
+-- @param layer The Layer that the Object belongs to
+-- @param object The index or name of the Object
+-- @return table List of properties
+function Map:getObjectProperties(layer, object)
+ local o = self.layers[layer].objects
+
+ if type(object) == "number" then
+ o = o[object]
+ else
+ for _, v in ipairs(o) do
+ if v.name == object then
+ o = v
+ break
+ end
+ end
+ end
+
+ if not o then
+ return {}
+ end
+
+ return o.properties
+end
+
+--- Change a tile in a layer to another tile
+-- @param layer The Layer that the Tile belongs to
+-- @param x The X axis location of the Tile (in tiles)
+-- @param y The Y axis location of the Tile (in tiles)
+-- @param gid The gid of the new tile
+function Map:setLayerTile(layer, x, y, gid)
+ layer = self.layers[layer]
+
+ layer.data[y] = layer.data[y] or {}
+ local tile = layer.data[y][x]
+ local instance
+ if tile then
+ local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
+ for _, inst in pairs(self.tileInstances[tile.gid]) do
+ if inst.x == tileX and inst.y == tileY then
+ instance = inst
+ break
+ end
+ end
+ end
+
+ if tile == self.tiles[gid] then
+ return
+ end
+
+ tile = self.tiles[gid]
+
+ if instance then
+ self:swapTile(instance, tile)
+ else
+ self:addNewLayerTile(layer, tile, x, y)
+ end
+ layer.data[y][x] = tile
+end
+
+--- Swap a tile in a spritebatch
+-- @param instance The current Instance object we want to replace
+-- @param tile The Tile object we want to use
+-- @return none
+function Map:swapTile(instance, tile)
+ -- Update sprite batch
+ if instance.batch then
+ if tile then
+ instance.batch:set(
+ instance.id,
+ tile.quad,
+ instance.x,
+ instance.y,
+ tile.r,
+ tile.sx,
+ tile.sy
+ )
+ else
+ instance.batch:set(
+ instance.id,
+ instance.x,
+ instance.y,
+ 0,
+ 0)
+
+ self.freeBatchSprites[instance.batch] =
+ self.freeBatchSprites[instance.batch] or {}
+
+ table.insert(self.freeBatchSprites[instance.batch], instance)
+ end
+ end
+
+ -- Remove old tile instance
+ for i, ins in ipairs(self.tileInstances[instance.gid]) do
+ if ins.batch == instance.batch and ins.id == instance.id then
+ table.remove(self.tileInstances[instance.gid], i)
+ break
+ end
+ end
+
+ -- Add new tile instance
+ if tile then
+ self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
+
+ local freeBatchSprites = self.freeBatchSprites[instance.batch]
+ local newInstance
+ if freeBatchSprites and #freeBatchSprites > 0 then
+ newInstance = freeBatchSprites[#freeBatchSprites]
+ freeBatchSprites[#freeBatchSprites] = nil
+ else
+ newInstance = {}
+ end
+
+ newInstance.layer = instance.layer
+ newInstance.batch = instance.batch
+ newInstance.id = instance.id
+ newInstance.gid = tile.gid or 0
+ newInstance.x = instance.x
+ newInstance.y = instance.y
+ newInstance.r = tile.r or 0
+ newInstance.oy = tile.r ~= 0 and tile.height or 0
+ table.insert(self.tileInstances[tile.gid], newInstance)
+ end
+end
+
+--- Convert tile location to pixel location
+-- @param x The X axis location of the point (in tiles)
+-- @param y The Y axis location of the point (in tiles)
+-- @return number The X axis location of the point (in pixels)
+-- @return number The Y axis location of the point (in pixels)
+function Map:convertTileToPixel(x,y)
+ if self.orientation == "orthogonal" then
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ return
+ x * tileW,
+ y * tileH
+ elseif self.orientation == "isometric" then
+ local mapH = self.height
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local offsetX = mapH * tileW / 2
+ return
+ (x - y) * tileW / 2 + offsetX,
+ (x + y) * tileH / 2
+ elseif self.orientation == "staggered" or
+ self.orientation == "hexagonal" then
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local sideLen = self.hexsidelength or 0
+
+ if self.staggeraxis == "x" then
+ return
+ x * tileW,
+ ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0)
+ else
+ return
+ ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0),
+ y * tileH
+ end
+ end
+end
+
+--- Convert pixel location to tile location
+-- @param x The X axis location of the point (in pixels)
+-- @param y The Y axis location of the point (in pixels)
+-- @return number The X axis location of the point (in tiles)
+-- @return number The Y axis location of the point (in tiles)
+function Map:convertPixelToTile(x, y)
+ if self.orientation == "orthogonal" then
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ return
+ x / tileW,
+ y / tileH
+ elseif self.orientation == "isometric" then
+ local mapH = self.height
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local offsetX = mapH * tileW / 2
+ return
+ y / tileH + (x - offsetX) / tileW,
+ y / tileH - (x - offsetX) / tileW
+ elseif self.orientation == "staggered" then
+ local staggerX = self.staggeraxis == "x"
+ local even = self.staggerindex == "even"
+
+ local function topLeft(x, y)
+ if staggerX then
+ if ceil(x) % 2 == 1 and even then
+ return x - 1, y
+ else
+ return x - 1, y - 1
+ end
+ else
+ if ceil(y) % 2 == 1 and even then
+ return x, y - 1
+ else
+ return x - 1, y - 1
+ end
+ end
+ end
+
+ local function topRight(x, y)
+ if staggerX then
+ if ceil(x) % 2 == 1 and even then
+ return x + 1, y
+ else
+ return x + 1, y - 1
+ end
+ else
+ if ceil(y) % 2 == 1 and even then
+ return x + 1, y - 1
+ else
+ return x, y - 1
+ end
+ end
+ end
+
+ local function bottomLeft(x, y)
+ if staggerX then
+ if ceil(x) % 2 == 1 and even then
+ return x - 1, y + 1
+ else
+ return x - 1, y
+ end
+ else
+ if ceil(y) % 2 == 1 and even then
+ return x, y + 1
+ else
+ return x - 1, y + 1
+ end
+ end
+ end
+
+ local function bottomRight(x, y)
+ if staggerX then
+ if ceil(x) % 2 == 1 and even then
+ return x + 1, y + 1
+ else
+ return x + 1, y
+ end
+ else
+ if ceil(y) % 2 == 1 and even then
+ return x + 1, y + 1
+ else
+ return x, y + 1
+ end
+ end
+ end
+
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+
+ if staggerX then
+ x = x - (even and tileW / 2 or 0)
+ else
+ y = y - (even and tileH / 2 or 0)
+ end
+
+ local halfH = tileH / 2
+ local ratio = tileH / tileW
+ local referenceX = ceil(x / tileW)
+ local referenceY = ceil(y / tileH)
+ local relativeX = x - referenceX * tileW
+ local relativeY = y - referenceY * tileH
+
+ if (halfH - relativeX * ratio > relativeY) then
+ return topLeft(referenceX, referenceY)
+ elseif (-halfH + relativeX * ratio > relativeY) then
+ return topRight(referenceX, referenceY)
+ elseif (halfH + relativeX * ratio < relativeY) then
+ return bottomLeft(referenceX, referenceY)
+ elseif (halfH * 3 - relativeX * ratio < relativeY) then
+ return bottomRight(referenceX, referenceY)
+ end
+
+ return referenceX, referenceY
+ elseif self.orientation == "hexagonal" then
+ local staggerX = self.staggeraxis == "x"
+ local even = self.staggerindex == "even"
+ local tileW = self.tilewidth
+ local tileH = self.tileheight
+ local sideLenX = 0
+ local sideLenY = 0
+
+ if staggerX then
+ sideLenX = self.hexsidelength
+ x = x - (even and tileW or (tileW - sideLenX) / 2)
+ else
+ sideLenY = self.hexsidelength
+ y = y - (even and tileH or (tileH - sideLenY) / 2)
+ end
+
+ local colW = ((tileW - sideLenX) / 2) + sideLenX
+ local rowH = ((tileH - sideLenY) / 2) + sideLenY
+ local referenceX = ceil(x) / (colW * 2)
+ local referenceY = ceil(y) / (rowH * 2)
+ local relativeX = x - referenceX * colW * 2
+ local relativeY = y - referenceY * rowH * 2
+ local centers
+
+ if staggerX then
+ local left = sideLenX / 2
+ local centerX = left + colW
+ local centerY = tileH / 2
+
+ centers = {
+ { x = left, y = centerY },
+ { x = centerX, y = centerY - rowH },
+ { x = centerX, y = centerY + rowH },
+ { x = centerX + colW, y = centerY },
+ }
+ else
+ local top = sideLenY / 2
+ local centerX = tileW / 2
+ local centerY = top + rowH
+
+ centers = {
+ { x = centerX, y = top },
+ { x = centerX - colW, y = centerY },
+ { x = centerX + colW, y = centerY },
+ { x = centerX, y = centerY + rowH }
+ }
+ end
+
+ local nearest = 0
+ local minDist = math.huge
+
+ local function len2(ax, ay)
+ return ax * ax + ay * ay
+ end
+
+ for i = 1, 4 do
+ local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY)
+
+ if dc < minDist then
+ minDist = dc
+ nearest = i
+ end
+ end
+
+ local offsetsStaggerX = {
+ { x = 0, y = 0 },
+ { x = 1, y = -1 },
+ { x = 1, y = 0 },
+ { x = 2, y = 0 },
+ }
+
+ local offsetsStaggerY = {
+ { x = 0, y = 0 },
+ { x = -1, y = 1 },
+ { x = 0, y = 1 },
+ { x = 0, y = 2 },
+ }
+
+ local offsets = staggerX and offsetsStaggerX or offsetsStaggerY
+
+ return
+ referenceX + offsets[nearest].x,
+ referenceY + offsets[nearest].y
+ end
+end
+
+--- A list of individual layers indexed both by draw order and name
+-- @table Map.layers
+-- @see TileLayer
+-- @see ObjectLayer
+-- @see ImageLayer
+-- @see CustomLayer
+
+--- A list of individual tiles indexed by Global ID
+-- @table Map.tiles
+-- @see Tile
+-- @see Map.tileInstances
+
+--- A list of tile instances indexed by Global ID
+-- @table Map.tileInstances
+-- @see TileInstance
+-- @see Tile
+-- @see Map.tiles
+
+--- A list of no-longer-used batch sprites, indexed by batch
+--@table Map.freeBatchSprites
+
+--- A list of individual objects indexed by Global ID
+-- @table Map.objects
+-- @see Object
+
+--- @table TileLayer
+-- @field name The name of the layer
+-- @field x Position on the X axis (in pixels)
+-- @field y Position on the Y axis (in pixels)
+-- @field width Width of layer (in tiles)
+-- @field height Height of layer (in tiles)
+-- @field visible Toggle if layer is visible or hidden
+-- @field opacity Opacity of layer
+-- @field properties Custom properties
+-- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles)
+-- @field update Update function
+-- @field draw Draw function
+-- @see Map.layers
+-- @see Tile
+
+--- @table ObjectLayer
+-- @field name The name of the layer
+-- @field x Position on the X axis (in pixels)
+-- @field y Position on the Y axis (in pixels)
+-- @field visible Toggle if layer is visible or hidden
+-- @field opacity Opacity of layer
+-- @field properties Custom properties
+-- @field objects List of objects indexed by draw order
+-- @field update Update function
+-- @field draw Draw function
+-- @see Map.layers
+-- @see Object
+
+--- @table ImageLayer
+-- @field name The name of the layer
+-- @field x Position on the X axis (in pixels)
+-- @field y Position on the Y axis (in pixels)
+-- @field visible Toggle if layer is visible or hidden
+-- @field opacity Opacity of layer
+-- @field properties Custom properties
+-- @field image Image to be drawn
+-- @field update Update function
+-- @field draw Draw function
+-- @see Map.layers
+
+--- Custom Layers are used to place userdata such as sprites within the draw order of the map.
+-- @table CustomLayer
+-- @field name The name of the layer
+-- @field x Position on the X axis (in pixels)
+-- @field y Position on the Y axis (in pixels)
+-- @field visible Toggle if layer is visible or hidden
+-- @field opacity Opacity of layer
+-- @field properties Custom properties
+-- @field update Update function
+-- @field draw Draw function
+-- @see Map.layers
+-- @usage
+-- -- Create a Custom Layer
+-- local spriteLayer = map:addCustomLayer("Sprite Layer", 3)
+--
+-- -- Add data to Custom Layer
+-- spriteLayer.sprites = {
+-- player = {
+-- image = lg.newImage("assets/sprites/player.png"),
+-- x = 64,
+-- y = 64,
+-- r = 0,
+-- }
+-- }
+--
+-- -- Update callback for Custom Layer
+-- function spriteLayer:update(dt)
+-- for _, sprite in pairs(self.sprites) do
+-- sprite.r = sprite.r + math.rad(90 * dt)
+-- end
+-- end
+--
+-- -- Draw callback for Custom Layer
+-- function spriteLayer:draw()
+-- for _, sprite in pairs(self.sprites) do
+-- local x = math.floor(sprite.x)
+-- local y = math.floor(sprite.y)
+-- local r = sprite.r
+-- lg.draw(sprite.image, x, y, r)
+-- end
+-- end
+
+--- @table Tile
+-- @field id Local ID within Tileset
+-- @field gid Global ID
+-- @field tileset Tileset ID
+-- @field quad Quad object
+-- @field properties Custom properties
+-- @field terrain Terrain data
+-- @field animation Animation data
+-- @field frame Current animation frame
+-- @field time Time spent on current animation frame
+-- @field width Width of tile
+-- @field height Height of tile
+-- @field sx Scale value on the X axis
+-- @field sy Scale value on the Y axis
+-- @field r Rotation of tile (in radians)
+-- @field offset Offset drawing position
+-- @field offset.x Offset value on the X axis
+-- @field offset.y Offset value on the Y axis
+-- @see Map.tiles
+
+--- @table TileInstance
+-- @field batch Spritebatch the Tile Instance belongs to
+-- @field id ID within the spritebatch
+-- @field gid Global ID
+-- @field x Position on the X axis (in pixels)
+-- @field y Position on the Y axis (in pixels)
+-- @see Map.tileInstances
+-- @see Tile
+
+--- @table Object
+-- @field id Global ID
+-- @field name Name of object (non-unique)
+-- @field shape Shape of object
+-- @field x Position of object on X axis (in pixels)
+-- @field y Position of object on Y axis (in pixels)
+-- @field width Width of object (in pixels)
+-- @field height Heigh tof object (in pixels)
+-- @field rotation Rotation of object (in radians)
+-- @field visible Toggle if object is visible or hidden
+-- @field properties Custom properties
+-- @field ellipse List of verticies of specific shape
+-- @field rectangle List of verticies of specific shape
+-- @field polygon List of verticies of specific shape
+-- @field polyline List of verticies of specific shape
+-- @see Map.objects
+
+return setmetatable({}, STI)
diff --git a/imperium-porcorum.love/core/modules/world/libs/sti/plugins/box2d.lua b/imperium-porcorum.love/core/modules/world/libs/sti/plugins/box2d.lua
new file mode 100644
index 0000000..6d2e1b4
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/sti/plugins/box2d.lua
@@ -0,0 +1,303 @@
+--- 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
diff --git a/imperium-porcorum.love/core/modules/world/libs/sti/plugins/bump.lua b/imperium-porcorum.love/core/modules/world/libs/sti/plugins/bump.lua
new file mode 100644
index 0000000..d69ff26
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/sti/plugins/bump.lua
@@ -0,0 +1,194 @@
+--- 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
diff --git a/imperium-porcorum.love/core/modules/world/libs/sti/utils.lua b/imperium-porcorum.love/core/modules/world/libs/sti/utils.lua
new file mode 100644
index 0000000..9f8839a
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/sti/utils.lua
@@ -0,0 +1,206 @@
+-- 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
diff --git a/imperium-porcorum.love/core/modules/world/libs/tsort.lua b/imperium-porcorum.love/core/modules/world/libs/tsort.lua
new file mode 100644
index 0000000..3f8dfc9
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/libs/tsort.lua
@@ -0,0 +1,84 @@
+-- See: https://github.com/bungle/lua-resty-tsort
+--
+-- Copyright (c) 2016, Aapo Talvensaari
+-- All rights reserved.
+--
+-- Redistribution and use in source and binary forms, with or without modification,
+-- are permitted provided that the following conditions are met:
+--
+-- * Redistributions of source code must retain the above copyright notice, this
+-- list of conditions and the following disclaimer.
+--
+-- * Redistributions in binary form must reproduce the above copyright notice, this
+-- list of conditions and the following disclaimer in the documentation and/or
+-- other materials provided with the distribution.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+-- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+-- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local function visit(k, n, m, s)
+ if m[k] == 0 then return 1 end
+ if m[k] == 1 then return end
+ m[k] = 0
+ local f = n[k]
+ for i=1, #f do
+ if visit(f[i], n, m, s) then return 1 end
+ end
+ m[k] = 1
+ s[#s+1] = k
+end
+local tsort = {}
+tsort.__index = tsort
+function tsort.new()
+ return setmetatable({ n = {} }, tsort)
+end
+function tsort:add(...)
+ local p = { ... }
+ local c = #p
+ if c == 0 then return self end
+ if c == 1 then
+ p = p[1]
+ if type(p) == "table" then
+ c = #p
+ else
+ p = { p }
+ end
+ end
+ local n = self.n
+ for i=1, c do
+ local f = p[i]
+ if n[f] == nil then n[f] = {} end
+ end
+ for i=2, c, 1 do
+ local f = p[i]
+ local t = p[i-1]
+ local o = n[f]
+ o[#o+1] = t
+ end
+ return self
+end
+function tsort:sort()
+ local n = self.n
+ local s = {}
+ local m = {}
+ for k in pairs(n) do
+ if m[k] == nil then
+ if visit(k, n, m, s) then
+ return nil, "There is a circular dependency in the graph. It is not possible to derive a topological sort."
+ end
+ end
+ end
+ return s
+end
+return tsort
diff --git a/imperium-porcorum.love/core/modules/world/world2D.lua b/imperium-porcorum.love/core/modules/world/world2D.lua
new file mode 100644
index 0000000..7375c46
--- /dev/null
+++ b/imperium-porcorum.love/core/modules/world/world2D.lua
@@ -0,0 +1,75 @@
+-- world2D.lua :: a basic 2D world based on bump2D.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.world2D$', '') .. "."
+
+local BaseWorld = require(cwd .. "baseworld")
+local World2D = BaseWorld:extend()
+
+local Sti = require(cwd .. "libs.sti")
+local Bump = require(cwd .. "libs.bump")
+local CameraSystem = require(cwd .. "camera")
+
+function World2D:new(scene, actorlist, mapfile)
+ World2D.super.new(self, scene, actorlist, mapfile)
+end
+
+-- ACTORS FUNCTIONS
+-- Wrappers around Bump2D functions
+
+function World2D:initActors()
+ self.currentCreationID = 0
+ self.actors = Bump.newWorld(50)
+end
+
+function World2D:registerActor(actor)
+ actor.creationID = self.currentCreationID
+ self.currentCreationID = self.currentCreationID + 1
+ return self.actors:add(actor, actor.x, actor.y, actor.w, actor.h)
+end
+
+function World2D:removeActor(actor)
+ return self.actors:remove(actor)
+end
+
+function World2D:moveActor(actor, x, y, filter)
+ return self.actors:move(actor, x, y, filter)
+end
+
+function World2D:checkCollision(actor, x, y, filter)
+ return self.actors:check(actor, x, y, filter)
+end
+
+function World2D:queryRect(x, y, w, h)
+ return self.actors:queryRect(x, y, w, h)
+end
+
+function World2D:countActors()
+ return self.actors:countItems()
+end
+
+function World2D:getActors()
+ return self.actors:getItems()
+end
+
+return World2D
diff --git a/imperium-porcorum.love/core/options.lua b/imperium-porcorum.love/core/options.lua
index 57111c9..361d211 100644
--- a/imperium-porcorum.love/core/options.lua
+++ b/imperium-porcorum.love/core/options.lua
@@ -24,12 +24,19 @@
local OptionsManager = Object:extend()
-local binser = require "libs.binser"
+local cwd = (...):gsub('%.options$', '') .. "."
+local binser = require(cwd .. "libs.binser")
-function OptionsManager:new()
+local TRANSLATION_PATH = "datas/languages/"
+
+-- INIT FUNCTIONS
+-- Initialize and configure the game options
+
+function OptionsManager:new(controller)
-- We begin by creating an empty data table before reading the data.
self.data = {}
self:read()
+ self.controller = controller
end
function OptionsManager:reset()
@@ -42,16 +49,19 @@ function OptionsManager:reset()
self.data.video.fullscreen = false
-- We load the default files
- self.data.input = require "datas.inputs"
+ self.data.input = self:getInputDefaultData()
-- TODO: have a way to auto-load a language according to the OS ?
- self.data.language = "en"
+ self.data.language = self:getTranslationDefaultData()
self.data.audio = {}
self.data.audio.music = 100
self.data.audio.sfx = 100
end
+-- INFO FUNCTIONS
+-- Get informations from the option managers
+
function OptionsManager:getFile(absolute)
local dir = ""
if absolute then
@@ -66,6 +76,71 @@ function OptionsManager:getFile(absolute)
return filepath
end
+function OptionsManager:getInputDefaultData()
+ local _path = "datas/inputs.lua"
+ local datas = {}
+ local fileinfo = love.filesystem.getInfo(_path)
+
+ if fileinfo ~= nil then
+ datas = require "datas.inputs"
+ else
+ datas = {}
+ end
+
+ return datas
+end
+
+function OptionsManager:getPlayerInputData(id)
+ local _playerInputData = self.data.input[id]
+
+ if _playerInputData == nil then
+ _playerInputData = {}
+ _playerInputData.keys = {}
+ end
+
+ return _playerInputData
+end
+
+function OptionsManager:getInputData()
+ return self.data.input
+end
+
+function OptionsManager:setInputKey(sourceid, padkey, key)
+ if self.data.input[sourceid] ~= nil then
+ if self.data.input[sourceid].keys[padkey] ~= nil then
+ self.data.input[sourceid].keys[padkey] = key
+ end
+ end
+end
+
+-- Lang data
+
+function OptionsManager:getTranslationDefaultData()
+ local _path = TRANSLATION_PATH .. "init.lua"
+ local fileinfo = love.filesystem.getInfo(_path)
+ local datas = nil
+
+ if fileinfo ~= nil then
+ lang = require(TRANSLATION_PATH)
+ lang.current = lang.default or "en"
+ lang.path = TRANSLATION_PATH
+ end
+
+ return lang
+end
+
+function OptionsManager:setLanguage(lang)
+ if (self.controller.lang:isLangAvailable(lang)) then
+ self.data.language.current = lang
+ self.controller.lang:getTranslationData()
+ end
+end
+
+-- DATA HANDLING FUNCTIONS
+-- Save and get data from the savefile
+
+-- FIXME: maybe subclass a special module for that ?
+
function OptionsManager:write()
local data = self:getData()
diff --git a/imperium-porcorum.love/core/scenemanager.lua b/imperium-porcorum.love/core/scenemanager.lua
index 5d403ca..b1a8af6 100644
--- a/imperium-porcorum.love/core/scenemanager.lua
+++ b/imperium-porcorum.love/core/scenemanager.lua
@@ -25,43 +25,96 @@
local SceneManager = Object:extend()
+-- INIT FUNCTIONS
+-- Initialize and configure the scene manager
+
function SceneManager:new(controller)
self.controller = controller
self.currentScene = nil
+
+ self.storage = {}
end
-function SceneManager:update(dt)
- if (self.currentScene ~= nil) then
- local keys = self.controller.input.keys
- self.currentScene.keys = keys
- self.currentScene.menusystem.keys = keys
- self.currentScene.assets:update(dt)
- self.currentScene.menusystem:update(dt)
- self.currentScene:update(dt)
+function SceneManager:setScene(scene)
+ self.currentScene = scene
+end
+
+function SceneManager:storeCurrentScene(name)
+ self.storage[name] = self.currentScene
+end
+
+function SceneManager:setStoredScene(name)
+ local storedScene = self.storage[name]
+ if storedScene ~= nil then
+ self.currentScene = storedScene
+ self.storage[name] = nil
end
end
-function SceneManager:mousemoved(x, y, dx, dy)
- self.currentScene.mouse.x,
- self.currentScene.mouse.y = x, y
- self.currentScene:mousemoved(x, y, dx, dy)
- self.currentScene.menusystem:mousemoved(x, y, dx, dy)
-end
-
-function SceneManager:mousepressed( x, y, button, istouch )
- self.currentScene:mousepressed( x, y, button, istouch )
- self.currentScene.menusystem:mousepressed( x, y, button, istouch )
+function SceneManager:clearStorage()
+ self.storage = {}
end
function SceneManager:clearScene()
self.currentScene = nil
end
+-- UPDATE FUNCTIONS
+-- Update the current scene and its subobjects
+
+function SceneManager:update(dt)
+ if (self.currentScene ~= nil) then
+ self.currentScene:updateStart(dt)
+ self.currentScene:setKeys()
+ self.currentScene.assets:update(dt)
+ self.currentScene.menusystem:update(dt)
+ self.currentScene:updateWorld(dt)
+ self.currentScene:update(dt)
+ self.currentScene:updateEnd(dt)
+ end
+end
+
+-- MOUSE FUNCTIONS
+-- Send pointer data to the scene
+
+function SceneManager:mousemoved(x, y, dx, dy)
+ if (self.currentScene ~= nil) then
+ self.currentScene.mouse.x,
+ self.currentScene.mouse.y = x, y
+ self.currentScene:mousemoved(x, y, dx, dy)
+ self.currentScene.menusystem:mousemoved(x, y, dx, dy)
+ end
+end
+
+function SceneManager:mousepressed( x, y, button, istouch )
+ if (self.currentScene ~= nil) then
+ self.currentScene:mousepressed( x, y, button, istouch )
+ self.currentScene.menusystem:mousepressed( x, y, button, istouch )
+ end
+end
+
+-- KEYBOARD FUNCTIONS
+-- Add send keys functions to the scene
+
+function SceneManager:keypressed( key, scancode, isrepeat )
+ self.currentScene:keypressed( key, scancode, isrepeat )
+end
+
+function SceneManager:keyreleased( key )
+ self.currentScene:keyreleased( key )
+end
+
+-- DRAW FUNCTIONS
+-- Draw the current scene
+
function SceneManager:draw()
self.controller.screen:apply()
if (self.currentScene ~= nil) then
- self.currentScene:draw(dt)
+ self.currentScene:drawStart()
+ self.currentScene:drawWorld()
+ self.currentScene:draw()
self.currentScene.menusystem:draw()
+ self.currentScene:drawEnd()
end
self.controller.screen:cease()
end
diff --git a/imperium-porcorum.love/core/screen.lua b/imperium-porcorum.love/core/screen.lua
index 4e962a8..ec3e6e0 100644
--- a/imperium-porcorum.love/core/screen.lua
+++ b/imperium-porcorum.love/core/screen.lua
@@ -24,7 +24,11 @@
local ScreenManager = Object:extend()
-local CScreen = require "libs.cscreen"
+local cwd = (...):gsub('%.screen$', '') .. "."
+local CScreen = require(cwd .. "libs.cscreen")
+
+-- INIT FUNCTIONS
+-- Initialize and configure the screen manager
function ScreenManager:new(controller)
self.controller = controller
@@ -51,6 +55,9 @@ function ScreenManager:applySettings()
CScreen.update(width, height)
end
+-- POINTER FUNCTIONS
+-- Translate the pointer according to the screen coordinates
+
function ScreenManager:project(x, y)
return CScreen.project(x, y)
end
@@ -59,10 +66,16 @@ function ScreenManager:getMousePosition()
return CScreen.project(love.mouse.getX(), love.mouse.getY())
end
+-- INFO FUNCTIONS
+-- Get screen informations
+
function ScreenManager:getDimensions()
return self.width, self.height
end
+-- DRAW FUNCTIONS
+-- Apply draw functions to the scene
+
function ScreenManager:apply()
CScreen.apply()
end
diff --git a/imperium-porcorum.love/core/utils/filesystem.lua b/imperium-porcorum.love/core/utils/filesystem.lua
new file mode 100644
index 0000000..ce99fbb
--- /dev/null
+++ b/imperium-porcorum.love/core/utils/filesystem.lua
@@ -0,0 +1,39 @@
+-- loveutils.filesystem : functions to handle filesystem.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Filesystem = {}
+
+function Filesystem.exists(filepath)
+ local info = love.filesystem.getInfo( filepath )
+ local exists = false
+
+ if (info == nil) then
+ exists = false
+ else
+ exists = true
+ end
+
+ return exists
+end
+
+return Filesystem
diff --git a/imperium-porcorum.love/core/utils/graphics.lua b/imperium-porcorum.love/core/utils/graphics.lua
new file mode 100644
index 0000000..925ddbe
--- /dev/null
+++ b/imperium-porcorum.love/core/utils/graphics.lua
@@ -0,0 +1,97 @@
+-- loveutils.graphics : a set of useful functions for love2D. Aim to reduce
+-- boilerplate in love2D by creating usefull function to handle these roles.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Graphics = {}
+
+-- COLOR FUNCTIONS
+-- Handle colors and scene colors
+
+function Graphics.resetColor()
+ love.graphics.setColor(1,1,1,1)
+end
+
+-- PRINT TEXT
+-- Functions to draw text on screen
+
+function Graphics.print(text, x, y, align, r, sx, sy, ox, oy, kx, ky)
+ local width
+ local font = love.graphics.getFont()
+ width = font:getWidth(text)
+
+ if align == "center" then
+ width = (width/2)
+ elseif align == "right" then
+ width = width
+ else
+ width = 0
+ end
+
+ love.graphics.print(text, x - (width), y, r, sx, sy, ox, oy, kx, ky)
+end
+
+function Graphics.printWithSpacing(text, spacing, align, x, y, r, sx, sy, ox, oy, kx, ky)
+ -- DO NOT USE THIS FUNCTION IN A "UPDATE" FUNCTION !
+ -- it's pretty heavy to use as it use a loop to get every character in a text
+ local font = love.graphics.getFont()
+ local xx = 0
+ local lenght = string.len(text)
+ local basewidth = font:getWidth(text)
+ local width = basewidth + (spacing * lenght)
+
+ if align == "center" then
+ width = (width/2)
+ elseif align == "right" then
+ width = width
+ else
+ width = 0
+ end
+
+ for i=1, lenght do
+ local char = string.sub(text, i, i)
+ pos = math.floor(x + xx - width)
+ love.graphics.print(char, pos, y)
+ xx = xx + font:getWidth(char) + spacing
+ end
+end
+
+-- PLACEHOLDER GRAPHICS FUNCTIONS
+-- Ready-to-use placeolder and stuff
+
+function Graphics.box(x, y, w, h)
+ local x = math.floor(x)
+ local y = math.floor(y)
+ local w = math.floor(w)
+ local h = math.floor(h)
+ local a = a or 1
+
+ local r, g, b, a = love.graphics.getColor( )
+
+ love.graphics.setColor(r, g, b, 0.3 * a)
+ love.graphics.rectangle("fill", x, y, w, h)
+
+ love.graphics.setColor(r, g, b, a)
+ love.graphics.rectangle("line", x, y, w, h)
+end
+
+return Graphics
diff --git a/imperium-porcorum.love/core/utils/init.lua b/imperium-porcorum.love/core/utils/init.lua
new file mode 100644
index 0000000..765016a
--- /dev/null
+++ b/imperium-porcorum.love/core/utils/init.lua
@@ -0,0 +1,31 @@
+-- loveutils : a set of basic functions and utility for love2D.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 cwd = (...):gsub('%.init$', '') .. "."
+
+-- load the different elements from loveutils
+return {
+ math = require(cwd .. "math"),
+ graphics = require(cwd .. "graphics"),
+ filesystem = require(cwd .. "filesystem")
+}
diff --git a/imperium-porcorum.love/core/utils/math.lua b/imperium-porcorum.love/core/utils/math.lua
new file mode 100644
index 0000000..fea382b
--- /dev/null
+++ b/imperium-porcorum.love/core/utils/math.lua
@@ -0,0 +1,139 @@
+-- loveutils.math : easy to use functions for mathematics and geometry.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ 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 Math = {}
+
+-- ALGEBRA FUNCTIONS
+-- Simple yet usefull functions not supported by base love2D
+
+function Math.sign(x)
+ if (x < 0) then
+ return -1
+ elseif (x > 0) then
+ return 1
+ else
+ return 0
+ end
+end
+
+function Math.round(num)
+ return math.floor(num + 0.5)
+end
+
+function Math.toZero(num, sub)
+ local sub = math.floor(sub)
+
+ if math.abs(num) < sub then
+ return 0
+ else
+ return num - (sub * Math.sign(num))
+ end
+end
+
+function Math.between(num, value1, value2)
+ local min = math.min(value1, value2)
+ local max = math.max(value1, value2)
+ return math.min(math.max(num, min), max)
+end
+
+-- VECTOR/DIRECTION functions
+-- Easy-to-use function to handle point and motion
+
+function Math.vector(x1, y1, x2, y2)
+ local vecx, vecy
+
+ vecx = x2 - x1
+ vexy = y2 - y1
+
+ return vecx, vecy
+end
+
+function Math.getMiddlePoint(x1, y1, x2, y2)
+ local newx, newy, vecx, vecy
+
+ vecx = math.max(x1, x2) - math.min(x1, x2)
+ vecy = math.max(y1, y2) - math.min(y1, y2)
+
+ newx = math.min(x1, x2) + (vecx / 2)
+ newy = math.min(y1, y2) + (vecy / 2)
+
+ return newx, newy
+end
+
+function Math.pointDistance(x1, y1, x2, y2)
+ local vecx, vecy
+
+ vecx = math.max(x1, x2) - math.min(x1, x2)
+ vecy = math.max(y1, y2) - math.min(y1, y2)
+
+ return math.sqrt(vecx^2 + vecy^2)
+
+end
+
+function Math.pointDirection(x1,y1,x2,y2)
+ local vecx, vecy, angle
+ vecy = y2 - y1
+ vecx = x2 - x1
+ angle = math.atan2(vecy, vecx)
+
+ return angle
+end
+
+-- STRING FUNCTIONS
+-- Transform into string numbers
+
+function Math.numberToString(x, length)
+ local length = length or 1
+ local string = ""
+ local x = x
+ if (x >= math.pow(10, length)) then
+ x = unitsNumber*10 - 1
+ string = string .. x
+ else
+ for i=1, (length-1) do
+ if (x < math.pow(10, length-i)) then
+ string = string .. "0"
+ end
+ end
+ string = string .. x
+ end
+ return string
+end
+
+-- COORDINATE FUNCTIONS
+-- Easy computation on coordinate
+
+function Math.floorCoord(x, y)
+ return math.floor(x), math.floor(y)
+end
+
+function Math.pixeliseCoord(x, y, factor)
+ x, y = Math.floorCoord(x / factor, y / factor)
+
+ x = x * factor
+ y = y * factor
+
+ return x, y
+end
+
+return Math