Rebase on Radiance #71

Merged
kazhnuz merged 20 commits from wip/radiance-rebase into master 2022-08-12 10:37:35 +02:00
145 changed files with 4561 additions and 2071 deletions

View File

@ -8,5 +8,8 @@
"core",
"scenes",
"game"
],
"Lua.diagnostics.disable": [
"redundant-parameter"
]
}

View File

@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
- Project renamed to Birb
- Project renamed to Birb and rebased entirely on Sonic Radiance codebase
- New loading system

View File

@ -1,10 +1,10 @@
# Birb Love2D Engine
Birb aim to be an integrated, simple engine for love2D. It aim to work as a set of managers to automatically handle inputs, screen, and several utilities to make game developpement easier and less repetitive.
Birb aim to be an integrated, simple engine for love2D, in replacement of my old "gamecore" love2D engine. It aim to work as a set of managers to automatically handle inputs, screen, and several utilities to make game developpement easier and less repetitive. It's also specialized in game with RPG mechanics, with functions to serialize/deserialize easily datas.
Birb use [Classic](https://github.com/rxi/classic/) as its base Object.
## How to load GameCore
## How to load Birb
The birb engine must be located in the `birb/` folder to work. After that, all you have to do is to load a gamecore based engine and then.

View File

@ -23,19 +23,22 @@
function love.update(dt)
core:update(dt)
if (game ~= nil) then
game:update(dt)
end
end
function love.draw()
core:draw()
end
function love.mousemoved(x, y, dx, dy)
core:mousemoved(x, y, dx, dy)
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.mousepressed( x, y, button, istouch )
-- core:mousepressed(x, y, button, istouch)
-- end
function love.keypressed( key, scancode, isrepeat )
core:keypressed( key, scancode, isrepeat )

View File

@ -23,7 +23,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Rect = require "birb.objects.2D.rect"
local Rect = require "birb.classes.2D.rect"
local IndexedRect = Rect:extend()
function IndexedRect:new(origin, ox, oy, w, h)

View File

@ -21,7 +21,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Point = require "birb.objects.2D.point"
local Point = require "birb.classes.2D.point"
local Rect = Point:extend()
function Rect:new(x, y, w, h)
@ -67,6 +67,10 @@ function Rect:getRelativeCoordinate(dx, dy)
return dx - self.x, dy - self.y
end
function Rect:getDimensions()
return self.w, self.h
end
function Rect:drawBox()
utils.graphics.box(self.x, self.y, self.w, self.h)
end

View File

@ -21,7 +21,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Point = require "birb.objects.3D.point3D"
local Point = require "birb.classes.3D.point3D"
local Box = Point:extend()
function Box:new(x, y, z, w, h, d)
@ -42,6 +42,7 @@ end
function Box:getShape()
local x, y, z, w, h, d = self:getArea()
return x, y-z-d, w, h+d
end
function Box:getCorners()

View File

@ -23,7 +23,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Rect = require "birb.objects.2D.rect"
local Rect = require "birb.classes.2D.rect"
local IndexedRect = Rect:extend()
function IndexedRect:new(origin, ox, oy, oz, w, h, d)

121
birb/classes/datapack.lua Normal file
View File

@ -0,0 +1,121 @@
-- datapack.lua :: a categorized and indexed pack of data. Aim to make easy to get
-- the category from an element, without having
--[[
Copyright © 2021 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 DataPack = Object:extend()
local UNCATEGORIZED = "uncategorized"
function DataPack:new(foldername, categories, areFiles, subfiles)
self.foldername = foldername
self.areFiles = (areFiles ~= false)
self.subfiles = subfiles or {}
self.categories = categories or {}
--print("loading " .. foldername)
self:loadAllDatas()
end
function DataPack:getBaseDirectory(lua)
local foldername = self.foldername
if (not lua) then
foldername = string.gsub(foldername, "%.", "/")
end
return utils.datas.concatDataFolder(foldername, lua)
end
function DataPack:getCategoryDirectory(category, lua)
if (category == UNCATEGORIZED) then
return self:getBaseDirectory(lua)
else
return utils.datas.concatFolder(self:getBaseDirectory(lua), category, lua)
end
end
function DataPack:haveCategories()
return (#self.categories > 0)
end
function DataPack:loadAllDatas()
self.index = {}
self.reverseIndex = {}
if (self:haveCategories()) then
for i, category in ipairs(self.categories) do
self:loadDatas(category)
end
else
self:loadDatas(UNCATEGORIZED)
end
end
function DataPack:loadDatas(category)
local directory = self:getCategoryDirectory(category)
local items = love.filesystem.getDirectoryItems(directory)
self.reverseIndex[category] = {}
for i, filename in ipairs(items) do
if (utils.datas.isLuaFile(filename) == self.areFiles) then
if (self.areFiles) then
filename = utils.datas.luaFileToModule(filename)
end
if (self.index[filename] ~= nil) then
error("Data " .. filename .. " already exists for " .. self.foldername)
end
--print("loading " .. filename .. " from category " .. category .. " for " .. self.foldername)
self.index[filename] = category
table.insert(self.reverseIndex[category], filename)
end
end
end
function DataPack:dataExists(name)
return (self.index[name] ~= nil)
end
function DataPack:getPath(name, lua)
if (not self:dataExists(name)) then
error("Data " .. name .. " do not exist for " .. self.foldername)
end
local category = self.index[name]
local categoryDirectory = self:getCategoryDirectory(category, true)
return utils.datas.concatFolder(categoryDirectory, name, lua)
end
function DataPack:get(name)
local path = self:getPath(name, true)
local data = utils.datas.copy(path)
if (not self.areFiles) then
for i, subfile in ipairs(self.subfiles) do
data[subfile] = utils.datas.copyDataset(path, subfile)
end
end
return data
end
function DataPack:getFromCategory(category)
return self.reverseIndex[category] or {}
end
return DataPack

63
birb/classes/parser.lua Normal file
View File

@ -0,0 +1,63 @@
-- parser.lua :: a data parser, used to parse data from arguments
--[[
Copyright © 2021 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 Parser = Object:extend()
function Parser:new(headings, argumentsList, argumentwrapper, nullableNbr)
self.headings = headings
self.argumentsList = argumentsList
self.argumentwrapper = argumentwrapper or ""
self.nullableNbr = nullableNbr or 0
end
function Parser:getArguments(name)
if (self.argumentsList[name] ~= nil) then
local arguments = {}
utils.table.mergeList(arguments, self.headings)
utils.table.mergeList(arguments, self.argumentsList[name])
return arguments
else
error("No arguments data for argumentList " .. name)
end
end
function Parser:parse(datas)
local arguments = self:getArguments(datas[1])
--print(utils.table.toString(arguments))
local parsedData = utils.table.parse(datas, arguments, self.nullableNbr)
if (utils.string.isEmpty(self.argumentwrapper)) then
-- print(utils.table.toString(parsedData))
return parsedData
else
local finalTable = {}
for i, heading in ipairs(self.headings) do
finalTable[heading] = parsedData[heading]
parsedData[heading] = nil
end
finalTable[self.argumentwrapper] = parsedData
-- print(utils.table.toString(finalTable))
return finalTable
end
end
return Parser

View File

@ -0,0 +1,115 @@
-- classes/predicates :: a predicate system, to be an intermediary between condition
-- solvers and complex conditions. You simply create a predicate with your data and the
-- solver to create a predicate, then use the Predicate:solve() API to get your response
-- To create a Predicate, prefer the Predicate.createPredicate instead of Predicate:new()
-- As it'll allow you to dynamically create simple or complex predicate on the fly
-- while Predicate:new() will try to create a complex predicate
--[[
Copyright © 2021 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 Predicate = Object:extend()
local SimplePredicate = require "birb.classes.predicate.simple"
function Predicate.createPredicate(data, solver, asker)
local predicate = nil
if (type(data) == "table") then
predicate = Predicate(data, solver, asker)
end
if (type(data) == "string") then
predicate = SimplePredicate(data, solver, asker)
end
if (predicate ~= nil) then
return predicate
else
error("Predicate data aren't a table or a string : " .. tostring(data))
end
end
function Predicate:new(data ,solver, asker)
self.solver = solver
self.asker = asker
self.beginAt = 1
self:setType(data)
self.notTag = false
self:setList(data)
end
function Predicate:solve()
local predicateSolved
if (self.type == "or") then
predicateSolved = self:solveOr()
else
predicateSolved = self:solveAnd()
end
if (self.notTag) then
predicateSolved = (predicateSolved == false)
end
return predicateSolved
end
-- INTERNAL FUNCTIONS
-- Handle the complex predicate
function Predicate:setType(data)
self.type = "and"
if (type(data[1]) == "string") then
if (data[1] == "or") or (data[1] == "and") then
self.beginAt = 2
self.type = data[1]
end
end
end
function Predicate:setList(data)
self.list = {}
for i = self.beginAt, #data, 1 do
if (i == #data) and (data[i] == "not") then
self.notTag = true
else
table.insert(self.list, Predicate.createPredicate(data[i], self.solver, self.asker))
end
end
end
function Predicate:solveOr()
for i, predicate in ipairs(self.list) do
if (predicate:solve()) then
return true
end
end
return false
end
function Predicate:solveAnd()
for i, predicate in ipairs(self.list) do
if (not predicate:solve()) then
return false
end
end
return true
end
return Predicate

View File

@ -0,0 +1,68 @@
-- classes/simple :: the actual solver of the predicate system. It parse a condition
-- system, and pass the data to a solver function. If the last argument of a condition
-- is "not", it'll negate the whole condition.
-- Each solver function will get as argument the list of conditions, the predicate and the
-- asker. The predicate contain an utility subclass with some function to handle easily
-- some stuff like converting truth tags or comparing numbers
-- An empty string will return true, a non-existing solver question an error, except if the solver have a
-- default function.
--[[
Copyright © 2021 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 SimplePredicate = Object:extend()
local SEPARATOR = ":"
local NOT = "not"
local DEFAULT = "default"
SimplePredicate.utils = require "birb.classes.predicate.utils"
function SimplePredicate:new(data, solver, asker)
self.solver = solver
self.asker = asker
self.data = data
end
function SimplePredicate:solve()
if (utils.string.isEmpty(self.data)) then
return true
end
local conditionArgs = utils.string.split(self.data, SEPARATOR)
local solverFunction = self:getFunction(conditionArgs[1])
local conditionFulfilled = solverFunction(conditionArgs, self, self.asker)
if (conditionArgs[#conditionArgs] == NOT) then
conditionFulfilled = (not conditionFulfilled)
end
return conditionFulfilled
end
function SimplePredicate:getFunction(functionName)
if (self.solver[functionName] == nil) then
if (self.solver[DEFAULT] == nil) then
error("Function " .. functionName .. " doesn't exist in solver, and it doesn't have a 'default' function.")
end
return self.solver[DEFAULT]
end
return self.solver[functionName]
end
return SimplePredicate

View File

@ -1,7 +1,7 @@
-- timer.lua :: a basic implementation of a timer for the actor system.
-- predicate/utils :: Simple utilities for predicates
--[[
Copyright © 2019 Kazhnuz
Copyright © 2021 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
@ -21,25 +21,45 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Timer = Object:extend()
local ConditionsUtils = {}
function Timer:new(controller, duration, name)
self.controller = controller
function ConditionsUtils.testVariables(var1, testType, var2)
local var2 = tonumber(var2)
if (testType == "eq" and var1 == var2) then
return true
end
if (testType == "ne" and var1 ~= var2) then
return true
end
if (testType == "gt" and var1 > var2) then
return true
end
if (testType == "ge" and var1 >= var2) then
return true
end
if (testType == "lt" and var1 < var2) then
return true
end
if (testType == "le" and var1 <= var2) then
return true
end
self.time = duration
self.name = name
return false
end
function Timer:update(dt)
self.time = self.time - dt
if (self.time <= 0) then
self:finish()
end
function ConditionsUtils.testBool(bool, boolType)
return (bool == (boolType == "V"))
end
function Timer:finish()
self.controller:timerResponse(self.name)
self.controller.timers[self.name] = nil
function ConditionsUtils.merge(cond)
local returnString = ""
for i, condElement in ipairs(cond) do
returnString = returnString .. condElement
if (i < #cond) then
returnString = returnString .. ":"
end
end
return returnString
end
return Timer
return ConditionsUtils;

View File

@ -0,0 +1,139 @@
-- classes/serializable :: a serializable object, can give its field as data
--[[
Copyright © 2021 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 Serializable = Object:extend()
function Serializable:new(serializeFields, listSerializable)
self.serializeFields = serializeFields
self.listSerializable = listSerializable
end
function Serializable:getData()
local data = {}
-- We serialize all fields
if (self.serializeFields ~= nil) then
for _, key in ipairs(self.serializeFields) do
data[key] = self:wrapField(key)
end
end
-- We serialize all list of serializable
if (self.listSerializable ~= nil) then
for _, key in ipairs(self.listSerializable) do
data[key] = self:wrapList(key)
end
end
return data
end
function Serializable:wrapField(key)
local serializedField = self[key]
if (serializedField ~= nil) then
if (type(serializedField) == "table" and serializedField.is ~= nil) then
if (serializedField:is(Serializable)) then
serializedField = serializedField:getData()
serializedField.isSerializable = true
end
end
self:print(key, serializedField, false)
return serializedField
end
end
function Serializable:wrapList(key)
local serializedList = {}
serializedList.isListSerializable = true
for listKey, listElement in pairs(self[key]) do
serializedList[listKey] = listElement:getData()
end
self:print(key, serializedList, false)
return serializedList
end
function Serializable:setData(data)
for key, value in pairs(data) do
if (key ~= "serializedField") then
self:unwrapField(key, value)
end
end
self:finishDeserialization()
end
function Serializable:unwrapField(key, value)
local serializedField = value
if (serializedField ~= nil) then
if (type(serializedField) == "table") then
if (serializedField.isSerializable == true) then
self[key]:setData(serializedField)
elseif (serializedField.isListSerializable) then
self:unwrapList(key, serializedField)
else
self[key] = serializedField or self[key]
end
else
self[key] = serializedField or self[key]
self:print(key, serializedField, true)
end
end
end
function Serializable:unwrapList(key, list)
if (list == nil) then
error("List " .. key .. " shouldn't be null in the save")
end
for listKey, listElement in pairs(list) do
if (listKey ~= "isListSerializable") then
self[key][listKey]:setData(listElement)
end
end
self:print(key, list, true)
end
function Serializable:print(key, value, isLoading)
local loadString = "Loading "
if (isLoading == false) then
loadString = "Saving "
end
local valueString = value
if type(valueString) == "boolean" then
if valueString then
valueString = "true"
else
valueString = "false"
end
end
if (type(value) == "table") then
valueString = utils.table.toString(value)
end
loadString = loadString .. key .. " " .. ": " .. valueString
if (core ~= nil) then
core.debug:debug("serializable", loadString)
else
--print("serializable", loadString)
end
end
function Serializable:finishDeserialization()
-- Empty function callback
end
return Serializable

View File

@ -0,0 +1,73 @@
-- classes/serializable/serializer :: a serializer, able to put stuff into a bin file
--[[
Copyright © 2021 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 Serializable = require "birb.classes.serializable"
local Serializer = Serializable:extend()
local binser = require("birb.libs.binser")
function Serializer:new(serializeFields, listSerializable)
Serializer.super.new(self, serializeFields, listSerializable)
end
function Serializer:reset()
end
function Serializer:delete(filename)
local filepath = self:getFile(true, filename)
if utils.filesystem.exists(filename) then
love.filesystem.remove(filepath)
end
end
function Serializer:deserialize(filename)
local filepath = self:getFile(true, filename)
if utils.filesystem.exists(filename) then
local loadedDatas = binser.readFile(filepath)
self:setData(loadedDatas[1])
else
self:reset()
end
end
function Serializer:serialize(filename)
local data = self:getData()
local filepath = self:getFile(true, filename)
binser.writeFile(filepath, data)
end
function Serializer:getFile(absolute, filename)
local dir = ""
if absolute then
dir = love.filesystem.getSaveDirectory() .. "/"
if (not utils.filesystem.exists(dir)) then
love.filesystem.createDirectory("")
end
end
local filepath = dir .. filename
return filepath
end
return Serializer

View File

@ -0,0 +1,14 @@
local Timer = require "birb.classes.time.timer"
local TimedFunction = Timer:extend()
function TimedFunction:new(actor, name, func, t)
TimedFunction.super.new(self, actor, name, t)
self.func = func
end
function TimedFunction:finish()
self.actor.funcs[self.name] = nil
self.func()
end
return TimedFunction

View File

@ -1,4 +1,6 @@
-- timers :: a basic implementation of a timer system.
-- time.lua :: a timer, tweener and timed switch handler.
-- This class need birb.libs.tween to work
--[[
Copyright © 2019 Kazhnuz
@ -21,57 +23,102 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local cwd = (...):gsub('%.init$', '') .. "."
local TweenManager = Object:extend()
local TimersManager = Object:extend()
local Timer = require(cwd .. "libs.timer")
local Tween = require(cwd .. "libs.tween")
local tween = require "birb.libs.tween"
local Timer = require "birb.classes.time.timer"
local TimedFunction = require "birb.classes.time.func"
function TimersManager:new(subject)
function TweenManager:new(subject)
self.subject = subject
self.time = 0
self.timers = {}
self.switches = {}
self.tweens = {}
self.switches = {}
self.timers = {}
self.funcs = {}
end
-- UPDATE
-- Update every timers
function TweenManager:newTween(start, duration, target, easing)
table.insert(self.tweens, self:createTween(start, duration, target, easing))
end
function TimersManager:update(dt)
self.time = self.time + dt
function TweenManager:setNamedTween(name, start, duration, target, easing)
self.tweens[name] = nil
self.tweens[name] = self:createTween(start, duration, target, easing)
end
for k, timer in pairs(self.timers) do
timer:update(dt)
end
function TweenManager:createTween(start, duration, target, easing)
local newTween = {}
-- we add the data into a tween wrapper
newTween.tween = tween.new(duration, self.subject, target, easing)
newTween.start = self.time + start -- /!\ START IS RELATIVE TO CURRENT TIME
newTween.clear = newTween.start + duration
self:updateSwitches(dt)
self:updateTweens(dt)
return newTween
end
function TweenManager:removeNamedTween(name)
self.tweens[name] = nil
end
function TweenManager:haveTween()
return #self.tweens > 0
end
-- TIMER FUNCTIONS
-- Handle the timers
-- Help to get info from timers
function TimersManager:newTimer(duration, name)
self.timers[name] = Timer(self, duration, name)
function TweenManager:newTimer(start, name)
self.timers[name] = Timer(self, name, start)
end
function TimersManager:timerResponse(timername)
if self.subject.timerResponse == nil then
core.debug:logWarn("TimersManager", "the subject have no timerResponse function")
return 0
function TweenManager:delayTimer(time, name, absolute)
if (self.timers[name] ~= nil) then
self.timers[name]:delay(time, absolute)
end
self.subject:timerResponse(timername)
end
function TweenManager:getTimerInfo(name)
if (self.timers[name] ~= nil) then
return self.timers[name]:getInfo()
end
end
function TweenManager:removeTimer(name)
self.timers[name] = nil
end
-- FUNCTION FUNCTIONS
-- Help to get functions
function TweenManager:newFunc(start, name, func)
self.funcs[name] = TimedFunction(self, name, func, start)
end
function TweenManager:delayFunc(time, name, absolute)
if (self.funcs[name] ~= nil) then
self.funcs[name]:delay(time, absolute)
end
end
function TweenManager:getFuncInfo(name)
if (self.funcs[name] ~= nil) then
return self.funcs[name]:getInfo()
end
end
function TweenManager:removeFunc(name)
self.funcs[name] = nil
end
-- SWITCH FUNCTIONS
-- Handle switches
-- Help to handle switches
function TimersManager:newSwitch(start, bools)
function TweenManager:newSwitch(start, bools)
local newSwitch = {}
-- we add the data into a switch wrapper
-- we add the data into a tween wrapper
newSwitch.bools = bools
newSwitch.start = self.time + start -- /!\ START IS RELATIVE TO CURRENT TIME
newSwitch.clear = newSwitch.start + 1
@ -79,8 +126,19 @@ function TimersManager:newSwitch(start, bools)
table.insert(self.switches, newSwitch)
end
function TimersManager:updateSwitches(dt)
for i, switch in ipairs(self.switches) do
function TweenManager:update(dt)
self.time = self.time + dt
for key, tweenWrapper in pairs(self.tweens) do
if (self.time > tweenWrapper.start) then
tweenWrapper.tween:update(dt)
if (self.time > tweenWrapper.clear) then
self.tweens[key] = nil
end
end
end
for i, switch in pairs(self.switches) do
if (self.time > switch.start) then
-- We test each boolean in the switch
for i, bool in ipairs(switch.bools) do
@ -95,32 +153,27 @@ function TimersManager:updateSwitches(dt)
table.remove(self.switches, i)
end
end
end
-- TWEENING FUNCTIONS
-- Handle tween support via tween.lua
for k, timer in pairs(self.timers) do
timer:update(dt)
end
function TimersManager:newTween(start, duration, target, easing)
local newTween = {}
-- we add the data into a tween wrapper
newTween.tween = Tween.new(duration, self.subject, target, easing)
newTween.start = self.time + start -- /!\ START IS RELATIVE TO CURRENT TIME
newTween.clear = newTween.start + duration
table.insert(self.tweens, newTween)
end
function TimersManager:updateTweens(dt)
for i, tweenWrapper in ipairs(self.tweens) do
if (self.time > tweenWrapper.start) then
tweenWrapper.tween:update(dt)
end
for k, func in pairs(self.funcs) do
func:update(dt)
end
self:clearEndedTweens()
end
function TimersManager:clearEndedTweens()
function TweenManager:timerResponse(timername)
if self.subject.timerResponse == nil then
core.debug:warning("tweenmanager", "the subject have no timerResponse function")
return 0
end
self.subject:timerResponse(timername)
end
function TweenManager:clearEndedTweens(dt)
for i, tweenWrapper in ipairs(self.tweens) do
if (self.time > tweenWrapper.clear) then
table.remove(self.tweens, i)
@ -128,4 +181,4 @@ function TimersManager:clearEndedTweens()
end
end
return TimersManager
return TweenManager

View File

@ -24,6 +24,7 @@
local Timer = Object:extend()
function Timer:new(actor, name, t)
self.baseTime = t
self.time = t
self.actor = actor
self.name = name
@ -36,9 +37,23 @@ function Timer:update(dt)
end
end
function Timer:delay(time, absolute)
if (absolute == true) then
self.baseTime = self.baseTime + (time - self.time)
self.time = time
else
self.baseTime = self.baseTime + time
self.time = self.time + time
end
end
function Timer:getInfo()
return self.time, self.baseTime, (self.time / self.baseTime)
end
function Timer:finish()
self.actor:timerResponse(self.name)
self.actor.timers[self.name] = nil
self.actor:timerResponse(self.name)
end
return Timer

View File

@ -1,105 +0,0 @@
-- core/assets :: a simple assets manager, aim to put every assets in a simple
-- serie of table in order to find them easily.
--[[
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 AssetManager = Object:extend()
function AssetManager:new()
self.locals = {}
self.globals = {}
self.updatables = {}
self.isActive = true
end
function AssetManager:update(dt)
if (self.isActive) then
for key, updatable in pairs(self.updatables) do
updatable:update(dt)
end
end
end
function AssetManager:add(name, asset, isGlobal)
if (isGlobal == true) then
self:addGlobal(name, asset)
else
self:addLocal(name, asset)
end
end
function AssetManager:addGlobal(name, asset)
self.globals[name] = asset
end
function AssetManager:addLocal(name, asset)
self.locals[name] = asset
end
function AssetManager:clear()
self.locals = {}
self.globals = {}
collectgarbage()
end
function AssetManager:clearLocal()
self.locals = {}
end
function AssetManager:get(name)
local asset
if self.locals[name] ~= nil then
asset = self.locals[name]
else
asset = self.globals[name]
end
if (asset ~= nil) then
return asset
else
core.debug:fail("core.assets", "L'asset " .. name .. " n'existe pas.")
end
end
-- Specific functions
function AssetManager:playSFX(name)
local asset = self:get(name)
asset:play()
end
-- Activity Functions
function AssetManager:setActivity(isActive)
self.isActive = isActive
end
function AssetManager:switchActivity()
self.isActive = (self.isActive == false)
end
function AssetManager:getActivity()
return self.isActive
end
return AssetManager

82
birb/core/datas.lua Normal file
View File

@ -0,0 +1,82 @@
-- datas.lua :: The main file of the core system, an object full of subsystem
-- loaded by the game to handle the main functions (like screen, translation,
-- inputs…)
--[[
Copyright © 2021 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 DataManager = Object:extend()
local DataPack = require "birb.classes.datapack"
local Parser = require "birb.classes.parser"
local index = require "datas.gamedata.index"
function DataManager:new(core)
self.core = core
self:loadDatas()
self:loadParsers()
end
function DataManager:loadDatas()
self.datapacks = {}
if (index.datapack ~= nil) then
for key, datas in pairs(index.datapacks) do
self.core.debug:debug("datamanager", "loading data for " .. key)
self.datapacks[key] = DataPack(datas[1], datas[2], datas[3], datas[4], datas[5])
end
end
end
function DataManager:get(datapack, name)
return self.datapacks[datapack]:get(name)
end
function DataManager:exists(datapack, name)
return self.datapacks[datapack]:dataExists(name)
end
function DataManager:getFromCategory(datapack, category)
return self.datapacks[datapack]:getFromCategory(category)
end
function DataManager:getCategories(datapack)
return self.datapacks[datapack].categories
end
-- Load parsers
function DataManager:loadParsers()
self.parsers = {}
local items = love.filesystem.getDirectoryItems("datas/parsers")
for i, item in ipairs(items) do
local filename = utils.datas.luaFileToModule(item)
local data = require("datas.parsers." .. filename)
self.core.debug:debug("datamanager", "creating parser for " .. filename)
self.parsers[filename] = Parser(data.headings, data.argumentLists, data.argumentWrapper, 0)
end
end
function DataManager:parse(parser, data)
return self.parsers[parser]:parse(data)
end
return DataManager

View File

@ -1,4 +1,4 @@
-- core/debug.lua :: A basic framework for debug.
-- core/debug.lua :: The basic internal debug framework of the birb engine.
--[[
Copyright © 2019 Kazhnuz
@ -23,128 +23,70 @@
local DebugSystem = Object:extend()
local lovebird = require("birb.libs.lovebird")
local lovebird
local ERROR = 0
local WARN = 1
local INFO = 2
local DEBUG = 3
local Levels = enum {
"ERROR",
"WARNING",
"INFO",
"DEBUG"
}
--- Create a new DebugSystem.
-- It allow you to determinate the debug level of the game.
-- @param controller the birb core
-- @param debugLevel the debug mode level
function DebugSystem:new(controller, debugLevel)
self.core = controller
self.debugLevel = debugLevel or 0
self.controller = controller
self:updateUI()
end
--- Update the DebugSystem.
-- @param dt the delta time
function DebugSystem:update(dt)
self:updateUI(dt)
end
--- Update the lovebird UI if the debug level is debug.
-- @param dt the delta time
function DebugSystem:updateUI(dt)
if (self:isDebug()) then
lovebird.update(dt)
self.debugLevel = debugLevel or Levels.WARNING
self.active = (self.debugLevel == Levels.DEBUG)
if (self.active) then
lovebird = require "birb.libs.lovebird"
lovebird.update()
end
end
-- TEST FUNCTIONS
-- Simple warper to get easily the debug level
--- Test if debug level is "DEBUG"
-- @return true if the debug level is "DEBUG"
function DebugSystem:isDebug()
return (self.debugLevel >= DEBUG)
end
--- Test if debug level is "INFO"
-- @return true if the debug level is "INFO"
function DebugSystem:isInfo()
return (self.debugLevel >= INFO)
end
--- Test if debug level is "WARN"
-- @return true if the debug level is "WARN"
function DebugSystem:isWarn()
return (self.debugLevel >= WARN)
end
--- Test if debug level is "ERROR"
-- @return true if the debug level is "ERROR"
function DebugSystem:isError()
return (self.debugLevel >= ERROR)
function DebugSystem:update(dt)
if (self.active and lovebird ~= nil) then
lovebird.update(dt)
end
end
-- PRINT FUNCTIONS
-- Print and log debug string
--- Log a debug line
--
-- @param context the context of the log
-- @param string the logged string
function DebugSystem:logDebug(context, string)
if (self:isDebug()) then
print(self:createLogLine("DEBUG", context, string))
function DebugSystem:debug(context, string)
self:log(Levels.DEBUG, context, string)
end
function DebugSystem:print(context, string)
self:log(Levels.INFO, context, string)
end
function DebugSystem:warning(context, string)
self:log(Levels.WARNING, context, string)
end
function DebugSystem:error(context, string)
self:log(Levels.ERROR, context, string)
error(self:getMessage(": ", context, string))
end
function DebugSystem:log(level, context, string)
if (self.debugLevel >= level) then
print(self:getLogLine(Levels[level], context, string))
end
end
--- Log an info
--
-- @param context the context of the log
-- @param string the logged string
function DebugSystem:logInfo(context, string)
if (self:isInfo()) then
print(self:createLogLine("INFO", context, string))
function DebugSystem:getLogLine(type, context, string)
local head = self.controller:getIdentity(false) .. "|" .. os.date("%x-%X") .. "|" .. type .. "|"
return head .. self:getMessage("|", context, string)
end
function DebugSystem:getMessage(separator, string1, string2)
if (string2 == nil) then
return string1
else
return string1 .. separator .. string2
end
end
--- Log a warning
--
-- @param context the context of the log
-- @param string the logged string
function DebugSystem:logWarn(context, string)
if (self:isWarn()) then
print(self:createLogLine("WARNING", context, string))
end
end
--- Log an error
--
-- @param context the context of the log
-- @param string the logged string
function DebugSystem:logError(context, string)
if (self:isError()) then
print(self:createLogLine("ERROR", context, string))
end
end
--- Log an error and fail
--
-- @param context the context of the log
-- @param string the logged string
function DebugSystem:fail(context, string)
if (self:isError()) then
print(self:createLogLine("ERROR", context, string))
error(string)
end
end
--- Create a formatted debug line
--
-- @param level the level of the log
-- @param context the context of the log
-- @param string the logged string
-- @return the debug line
function DebugSystem:createLogLine(level, context, string)
local head = self.core:getName() .. "|" .. os.date("%x-%X") .. "|"
return head .. level .. "|" .. context .. ": " .. string;
end
return DebugSystem

View File

@ -28,33 +28,37 @@ local cwd = (...):gsub('%.init$', '') .. "."
local CoreSystem = Object:extend()
local DebugSystem = require(cwd .. "debug")
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")
local MusicManager = require(cwd .. "music")
local Assets = require(cwd .. "assets")
local DataManager = require(cwd .. "datas")
-- INIT FUNCTIONS
-- Initialize and configure the core object
function CoreSystem:new(debugLevel)
local conf = self:getDefaultConf()
self.gameName = conf.identity
function CoreSystem:new(args)
self.args = args
self:setDefaultConf()
self.modules = birb.modules
self.debug = DebugSystem(self, debugLevel)
self.debug = DebugSystem(self, self:getArg("DEBUGLEVEL", true))
self.options = Options(self)
self.input = Input(self)
self.screen = Screen(self)
self.scenemanager = SceneManager(self)
self.lang = Lang(self)
self.music = MusicManager(self)
self.assets = Assets(self)
self.datas = DataManager(self)
end
self.debug:logDebug("birbcore","Birb initialized")
function CoreSystem:setDefaultConf()
self.conf = {}
self.conf.window = {}
self.conf.modules = {}
love.conf(self.conf)
end
function CoreSystem:registerGameSystem(gamesystem)
@ -62,17 +66,29 @@ function CoreSystem:registerGameSystem(gamesystem)
end
function CoreSystem:getDefaultConf()
local defaultConf = {}
defaultConf.window = {}
defaultConf.modules = {}
love.conf(defaultConf)
return defaultConf
return self.conf
end
function CoreSystem:getName()
return self.gameName
function CoreSystem:getIdentity(versionned)
local identity = self.conf.identity or "birbengine"
if (versionned) then
local version = self.conf.gameversion or 0
identity = identity .. "-" .. version
end
return identity
end
function CoreSystem:getArg(name, isNumber)
for _, arg in ipairs(self.args) do
local argData = utils.string.split(arg, "=")
if (argData[1] == name) then
if (isNumber == true) then
return tonumber(argData[2])
else
return argData[2]
end
end
end
end
-- MOUSE FUNCTIONS
@ -104,25 +120,26 @@ end
-- Load every sytem every update functions of the scene and objects
function CoreSystem:update(dt)
-- If the frameskip is to high, prefer slowdown to frameskip
local dt = math.min(dt, 1/15)
self.debug:update(dt)
self.input:update(dt)
self.screen:update(dt)
self.music:update(dt)
self.assets:update(dt)
if (self.game ~= nil) then
self.game:update(dt)
end
self.scenemanager:update(dt)
self.screen:update(dt)
self.music:update(dt)
end
-- DRAW FUNCTIONS
-- Draw the whole game
function CoreSystem:draw()
self.scenemanager:redraw()
self.scenemanager:draw()
self.screen:drawFade()
end
-- EXIT FUNCTIONS

View File

@ -143,7 +143,7 @@ function VirtualPad:isDown(key)
isdown = love.keyboard.isDown(self.data.keys[key])
else
local warnstring = "unsupported input device " .. self.type .. " for source " .. self.id
core.debug:logWarn("core/input", warnstring)
core.debug:warning("core/input", warnstring)
end
return isdown
@ -159,13 +159,13 @@ function VirtualPad:checkKey(key)
local isDown = self:isDown(key)
if (isDown) then
if not (self.keys[key].isDown) then
core.debug:logDebug("virtualpad", "key " .. key .. " is Pressed")
--core.debug:print("virtualpad", "key " .. key .. " is Pressed")
self.keys[key].isDown = true
self.keys[key].isPressed = true
self.keys[key].isReleased = false
else
if (self.keys[key].isPressed) then
core.debug:logDebug("virtualpad", "key " .. key .. " is Down")
--core.debug:print("virtualpad", "key " .. key .. " is Down")
self.keys[key].isPressed = false
end
end
@ -176,7 +176,7 @@ function VirtualPad:checkKey(key)
self.keys[key].isReleased = true
else
if (self.keys[key].isReleased) then
core.debug:logDebug("virtualpad", "key " .. key .. " is Released")
--core.debug:print("virtualpad", "key " .. key .. " is Released")
self.keys[key].isReleased = false
end
end

View File

@ -95,7 +95,7 @@ function LanguageManager:getTranslationStringList(lang, library)
if fileinfo ~= nil then
list = require(_path)
else
core.debug:logWarn("core/lang","file " .. _path .. " do not exists")
core.debug:warning("core/lang","file " .. _path .. " do not exists")
end
return list
@ -120,7 +120,7 @@ function LanguageManager:translate(library, string)
if (translation == nil) then
translation = string
core.debug:logWarn("core/lang", "no translation path found for " .. string .. " in " .. library)
core.debug:warning("core/lang", "no translation path found for " .. string .. " in " .. library)
end
return translation

View File

@ -1,4 +1,4 @@
-- core/langs.lua :: A music system, in order to add some usefull features
-- core/music.lua :: A music system, in order to add some usefull features
-- and to make music not scene dependant
--[[
@ -54,10 +54,6 @@ function MusicManager:haveMusic()
return (self.musics[1] ~= nil)
end
function MusicManager:applyVolume()
self.musics[1]:setVolume(self.core.options.data.audio.music / 100)
end
function MusicManager:playMusic()
if (self:haveMusic()) then
self.musics[1]:setVolume(self.core.options.data.audio.music / 100)
@ -87,7 +83,7 @@ function MusicManager:silence()
end
end
function MusicManager:skipMusic()
function MusicManager:skip()
if (self:haveMusic()) then
self.musics[1]:stop()
self.isPlaying = false

View File

@ -21,34 +21,36 @@
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 OptionsManager = Object:extend()
local binser = require("birb.libs.binser")
local Serializer = require "birb.classes.serializable.serializer"
local OptionsManager = Serializer:extend()
local TRANSLATION_PATH = "datas/languages/"
local OPTION_FILE = "options.data"
-- INIT FUNCTIONS
-- Initialize and configure the game options
function OptionsManager:new(core)
self.core = core
function OptionsManager:new(controller)
OptionsManager.super.new(self, {"data"})
self.controller = controller
-- We begin by creating an empty data table before reading the data.
self.data = {}
self:reset()
self:read()
end
function OptionsManager:reset()
-- load the default set of config
local defaultConf = self.core:getDefaultConf()
local conf = self.controller:getDefaultConf()
-- Reset the option to the game defaults.
self.data = {}
self.data.video = {}
self.data.video.crtfilter = false
self.data.video.resolution = defaultConf.window.resolution
self.data.video.borderless = defaultConf.window.borderless
self.data.video.vsync = defaultConf.window.vsync
self.data.video.fullscreen = defaultConf.window.fullscreen
self.data.video.crtfilter = (conf.window.crtfilter == true)
self.data.video.resolution = conf.window.resolution or 1
self.data.video.border = (conf.window.borderless == false)
self.data.video.vsync = (conf.window.vsync ~= false)
self.data.video.fullscreen = (conf.window.fullscreen == true)
-- We load the default files
self.data.input = self:getInputDefaultData()
@ -57,27 +59,13 @@ function OptionsManager:reset()
self.data.language = self:getTranslationDefaultData()
self.data.audio = {}
self.data.audio.music = defaultConf.volume.music
self.data.audio.sfx = defaultConf.volume.sfx
self.data.audio.music = conf.volume.music or 100
self.data.audio.sfx = conf.volume.sfx or 100
end
-- INFO FUNCTIONS
-- Get informations from the option managers
function OptionsManager:getFile(absolute)
local dir = ""
if absolute then
dir = love.filesystem.getSaveDirectory() .. "/"
if not utils.filesystem.exists(dir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = dir .. "options.data"
return filepath
end
function OptionsManager:getInputDefaultData()
local _path = "datas/inputs.lua"
local datas = {}
@ -121,8 +109,7 @@ function OptionsManager:getTranslationDefaultData()
local _path = TRANSLATION_PATH .. "init.lua"
local fileinfo = love.filesystem.getInfo(_path)
local datas = nil
local lang = nil
local lang
if fileinfo ~= nil then
lang = require(TRANSLATION_PATH)
lang.current = lang.default or "en"
@ -133,9 +120,9 @@ function OptionsManager:getTranslationDefaultData()
end
function OptionsManager:setLanguage(lang)
if (self.core.lang:isLangAvailable(lang)) then
if (self.controller.lang:isLangAvailable(lang)) then
self.data.language.current = lang
self.core.lang:getTranslationData()
self.controller.lang:getTranslationData()
end
end
@ -145,30 +132,11 @@ end
-- FIXME: maybe subclass a special module for that ?
function OptionsManager:write()
local data = self:getData()
local filepath = self:getFile(true)
binser.writeFile(filepath, data)
self:serialize(OPTION_FILE)
end
function OptionsManager:read()
local filepath = self:getFile(true)
if utils.filesystem.exists("options.data") then
local loadedDatas = binser.readFile(filepath)
self.core.debug:logInfo("core/options", "data file found, loading it")
self:setData(loadedDatas[1])
else
self:reset()
self.core.debug:logInfo("core/options", "no data file found, reseting data")
end
end
function OptionsManager:getData(data)
return self.data
end
function OptionsManager:setData(data)
self.data = data
self:deserialize(OPTION_FILE)
end
return OptionsManager

View File

@ -30,22 +30,19 @@ local SceneManager = Object:extend()
function SceneManager:new(controller)
self.controller = controller
self.timers = self.controller.modules.Timers(self)
self.currentScene = nil
self.nextScene = nil
self.storage = {}
self:initTransitions()
end
function SceneManager:setScene(scene)
if self.transition.isPrepared then
self:startTransition(scene)
else
self.currentScene = scene
self.currentScene:start()
self.currentScene.isActive = true
end
--self.currentScene = nil
self.nextScene = scene
end
function SceneManager:haveStoredScene(name)
return (self.storage[name] ~= nil)
end
function SceneManager:storeCurrentScene(name)
@ -57,6 +54,8 @@ function SceneManager:setStoredScene(name)
if storedScene ~= nil then
self.currentScene = storedScene
self.storage[name] = nil
collectgarbage()
self.currentScene:restored()
end
end
@ -72,7 +71,12 @@ end
-- Update the current scene and its subobjects
function SceneManager:update(dt)
self.timers:update(dt)
if (self.nextScene ~= nil) then
self.currentScene = self.nextScene
self.nextScene = nil
collectgarbage()
end
if (self.currentScene ~= nil) then
self.currentScene:updateScene(dt)
end
@ -86,14 +90,12 @@ 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
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
@ -101,55 +103,14 @@ end
-- Add send keys functions to the scene
function SceneManager:keypressed( key, scancode, isrepeat )
self.currentScene:keypressed( key, scancode, isrepeat )
if (self.currentScene ~= nil) then
self.currentScene:keypressed( key, scancode, isrepeat )
end
end
function SceneManager:keyreleased( key )
self.currentScene:keyreleased( key )
end
-- TRANSITION FUNCTIONS
-- Prepare transitionning to the next scene
function SceneManager:initTransitions()
self.transition = {}
self.transition.easeIn = "inQuad"
self.transition.easeOut = "outQuad"
self.transition.duration = 1
self.transition.nextScene = nil
self.transition.isPrepared = false
end
function SceneManager:prepareTransition(duration, easeIn, easeOut)
self.transition.easeIn = easeIn or self.transition.easeIn
self.transition.easeOut = easeOut or self.transition.easeOut
self.transition.duration = duration or self.transition.duration
self.transition.isPrepared = true
end
function SceneManager:startTransition(nextScene)
self.transition.nextScene = nextScene
self.currentScene:flushKeys(self.transition.duration)
self.currentScene.isActive = false
self.transition.nextScene.isActive = false
core.screen:fadeIn(self.transition.duration / 2.5, self.transition.easeIn)
self.timers:newTimer(self.transition.duration / 2, "fadeOut")
end
function SceneManager:timerResponse(timer)
if timer == "fadeOut" then
self.currentScene = self.transition.nextScene
self.currentScene:start()
self.currentScene:flushKeys(self.transition.duration / 2.5)
self.currentScene.isActive = false
core.screen:fadeOut(self.transition.duration / 2.5, self.transition.easeOut)
self.transition.isPrepared = false
self.timers:newTimer(self.transition.duration / 2.5, "activateScene")
elseif timer == 'activateScene' then
self.currentScene.isActive = true
if (self.currentScene ~= nil) then
self.currentScene:keyreleased( key )
end
end
@ -164,4 +125,8 @@ function SceneManager:draw()
self.controller.screen:cease()
end
function SceneManager:redraw()
self.currentScene:redraw()
end
return SceneManager

View File

@ -39,9 +39,28 @@ function ScreenManager:new(controller)
love.graphics.setDefaultFilter( "nearest", "nearest", 1 )
self.timers = self.controller.modules.Timers(self)
self.transition = nil
self.transitionOut = nil
self.isOpaque = false
end
self.transitionValue = 0
function ScreenManager:startTransition(transitionIn, transitionOut, func, x, y)
self.transition = transitionIn(func, x, y, false)
self.transitionOut = transitionOut(nil, x, y, true)
self.isOpaque = true
end
function ScreenManager:transitionOver(fadeOut)
if (not fadeOut) then
self.transition = self.transitionOut
else
self.transition = nil
self.isOpaque = false
end
end
function ScreenManager:isActive()
return ((self.transition == nil) and (not self.isOpaque))
end
function ScreenManager:applySettings()
@ -49,7 +68,7 @@ function ScreenManager:applySettings()
local flags = {}
flags.vsync = self.data.vsync
flags.borderless = (self.data.borderless)
flags.borderless = (self.data.border == false)
love.window.setMode(self.width * self.data.resolution, self.height * self.data.resolution, flags)
love.window.setFullscreen( self.data.fullscreen )
@ -58,6 +77,12 @@ function ScreenManager:applySettings()
CScreen.update(width, height)
end
function ScreenManager:update(dt)
if (self.transition ~= nil) then
self.transition:update(dt)
end
end
-- POINTER FUNCTIONS
-- Translate the pointer according to the screen coordinates
@ -96,35 +121,6 @@ function ScreenManager:resetScissor()
love.graphics.setScissor( )
end
-- UPDATE FUNCTIONS
-- Update the screen
function ScreenManager:update(dt)
self.timers:update(dt)
end
-- TRANSITION FUNCTIONS
-- Handle transitions
function ScreenManager:fadeIn(duration, easing)
local duration = duration or 1
local easing = easing or "inExpo"
self.timers:newTween(0, duration, {transitionValue = 1}, easing)
end
function ScreenManager:fadeOut(duration, easing)
local duration = duration or 1
local easing = easing or "outExpo"
self.timers:newTween(0, duration, {transitionValue = 0}, easing)
end
function ScreenManager:drawFade()
local w, h = love.graphics.getDimensions()
love.graphics.setColor(0, 0, 0, self.transitionValue)
love.graphics.rectangle("fill", 0, 0, w, h)
utils.graphics.resetColor()
end
-- DRAW FUNCTIONS
-- Apply draw functions to the scene
@ -136,4 +132,16 @@ function ScreenManager:cease()
CScreen.cease()
end
function ScreenManager:drawTransition()
if (self.isOpaque) then
if (self.transition ~= nil) then
self.transition:draw()
else
love.graphics.setColor(0,0,0,1)
love.graphics.rectangle("fill",0,0,424,240)
utils.graphics.resetColor()
end
end
end
return ScreenManager

102
birb/gamesystem/init.lua Normal file
View File

@ -0,0 +1,102 @@
-- GameSystem :: The main GameSystem subsystem. Basically a big object that handle all the
-- GameSystem-related data like characters, monsters, etc. While the core aim to be
-- reusable at will, the GameSystem is specifically made for the current GameSystem.
--[[
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 Serializer = require "birb.classes.serializable.serializer"
local GameSystem = Serializer:extend()
local VAR_TO_SERIALIZE = {
"gametime",
"destroyedGizmo",
"variables",
"flags"
}
function GameSystem:new()
self.slot = -1
self.slotNumber = 3
self.version = core.conf.gameversion or "N/A"
self:reset()
GameSystem.super.new(self, VAR_TO_SERIALIZE)
end
function GameSystem:reset()
self.gametime = 0
self.flags = {}
self.destroyedGizmo = {}
self.variables = {}
self.mapName = ""
end
function GameSystem:reload()
self:read(self.slot)
end
function GameSystem:read(save_id)
self.slot = save_id
if (self.slot > 0) then
self:deserialize(self:getSaveName())
end
end
function GameSystem:deleteCurrentSave()
if (self.slot > 0) then
self:delete(self:getSaveName())
self.metadata:remove(self.slot)
end
end
function GameSystem:write()
if (self.slot > 0) then
self:serialize(self:getSaveName())
end
end
function GameSystem:getSaveName(saveslot)
local saveslot = saveslot or self.slot
return "save" .. saveslot .. ".save"
end
function GameSystem:resetSaves()
for i=1, self.slotNumber do
self:delete(self:getSaveName(i))
end
end
function GameSystem:update(dt)
self.gametime = self.gametime + dt
end
function GameSystem:getTime()
return utils.time.getFields(self.gametime)
end
function GameSystem:getTimeString()
return utils.time.toString(self.gametime)
end
function GameSystem:printTime()
core.debug:print(self:getTimeString())
end
return GameSystem

View File

@ -1,8 +1,7 @@
-- birb/init.lua :: The main file of birb, that initilize the whole birb engine
-- It basically works by loading everything needed for a full birb experience.
-- birb :: The main birb script, loading the core and main utilities
--[[
Copyright © 2019 Kazhnuz
Copyright © 2021 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
@ -30,12 +29,24 @@ birb = {}
Object = require("birb.libs.classic")
utils = require("birb.utils")
enum = require("birb.utils.enum")
birb.modules = require("birb.modules")
birb.Core = require("birb.core")
function birb.startCore(debugLevel)
core = birb.Core(debugLevel)
function birb.start(gamemodule, args)
birb.startCore(args)
if (gamemodule ~= nil) then
birb.startGame(gamemodule)
end
end
require("birb.callbacks")
function birb.startCore(args)
core = birb.Core(args)
end
function birb.startGame(gamemodule)
local GameObject = require(gamemodule)
game = GameObject()
end
require("birb.callbacks")

392
birb/libs/talkies.lua Normal file
View File

@ -0,0 +1,392 @@
--
-- talkies
--
-- Copyright (c) 2017 twentytwoo, tanema
--
-- 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 utf8 = require("utf8")
local function playSound(sound, pitch)
if type(sound) == "userdata" then
sound:setPitch(pitch or 1)
sound:play()
end
end
local function parseSpeed(speed)
if speed == "fast" then return 0.01
elseif speed == "medium" then return 0.04
elseif speed == "slow" then return 0.08
else
assert(tonumber(speed), "setSpeed() - Expected number, got " .. tostring(speed))
return speed
end
end
local Fifo = {}
function Fifo.new () return setmetatable({first=1,last=0},{__index=Fifo}) end
function Fifo:peek() return self[self.first] end
function Fifo:len() return (self.last+1)-self.first end
function Fifo:push(value)
self.last = self.last + 1
self[self.last] = value
end
function Fifo:pop()
if self.first > self.last then return end
local value = self[self.first]
self[self.first] = nil
self.first = self.first + 1
return value
end
local Typer = {}
function Typer.new(msg, speed)
local timeToType = parseSpeed(speed)
return setmetatable({
msg = msg, complete = false, paused = false,
timer = timeToType, max = timeToType, position = 0, visible = "",
},{__index=Typer})
end
function Typer:resume()
if not self.paused then return end
self.msg = self.msg:gsub("%-%-", " ", 1)
self.paused = false
end
function Typer:finish()
if self.complete then return end
self.msg = self.msg:gsub("%-%-", " ")
self.visible = self.msg
self.complete = true
end
function Typer:update(dt)
local typed = false
if self.complete then return typed end
if not self.paused then
self.timer = self.timer - dt
while not self.paused and not self.complete and self.timer <= 0 do
typed = string.sub(self.msg, self.position, self.position) ~= " "
self.position = self.position + 1
self.timer = self.timer + self.max
self.visible = string.sub(self.msg, 0, utf8.offset(self.msg, self.position) - 1)
self.complete = (self.visible == self.msg)
self.paused = string.sub(self.msg, string.len(self.visible)+1, string.len(self.visible)+2) == "--"
end
end
return typed
end
local Talkies = {
_VERSION = '0.0.1',
_URL = 'https://github.com/tanema/talkies',
_DESCRIPTION = 'A simple messagebox system for LÖVE',
-- Theme
indicatorCharacter = ">>",
optionCharacter = ">",
padding = 4,
talkSound = nil,
optionSwitchSound = nil,
optionOnSelectSound = nil,
inlineOptions = true,
titleColor = {1, 1, 1},
titleBackgroundColor = nil,
titleBorderColor = nil,
messageColor = {1, 1, 1},
messageBackgroundColor = {0, 0, 0, 0.8},
messageBorderColor = nil,
rounding = 0,
thickness = 0,
textSpeed = 1 / 60,
font = love.graphics.newFont(),
typedNotTalked = true,
pitchValues = {0.7, 0.8, 1.0, 1.2, 1.3},
indicatorTimer = 0,
indicatorDelay = 3,
showIndicator = false,
dialogs = Fifo.new(),
}
function Talkies.say(title, messages, config)
config = config or {}
if type(messages) ~= "table" then
messages = { messages }
end
msgFifo = Fifo.new()
for i=1, #messages do
msgFifo:push(Typer.new(messages[i], config.textSpeed or Talkies.textSpeed))
end
local font = config.font or Talkies.font
-- Insert the Talkies.new into its own instance (table)
local newDialog = {
title = title or "",
messages = msgFifo,
image = config.image,
options = config.options,
onstart = config.onstart or function(dialog) end,
onmessage = config.onmessage or function(dialog, left) end,
oncomplete = config.oncomplete or function(dialog) end,
-- theme
indicatorCharacter = config.indicatorCharacter or Talkies.indicatorCharacter,
optionCharacter = config.optionCharacter or Talkies.optionCharacter,
padding = config.padding or Talkies.padding,
rounding = config.rounding or Talkies.rounding,
thickness = config.thickness or Talkies.thickness,
talkSound = config.talkSound or Talkies.talkSound,
optionSwitchSound = config.optionSwitchSound or Talkies.optionSwitchSound,
optionOnSelectSound = config.optionOnSelectSound or Talkies.optionOnSelectSound,
inlineOptions = config.inlineOptions or Talkies.inlineOptions,
font = font,
fontHeight = font:getHeight(" "),
typedNotTalked = config.typedNotTalked == nil and Talkies.typedNotTalked or config.typedNotTalked,
pitchValues = config.pitchValues or Talkies.pitchValues,
optionIndex = 1,
showOptions = function(dialog) return dialog.messages:len() == 1 and type(dialog.options) == "table" end,
isShown = function(dialog) return Talkies.dialogs:peek() == dialog end
}
newDialog.messageBackgroundColor = config.messageBackgroundColor or Talkies.messageBackgroundColor
newDialog.titleBackgroundColor = config.titleBackgroundColor or Talkies.titleBackgroundColor or newDialog.messageBackgroundColor
newDialog.messageColor = config.messageColor or Talkies.messageColor
newDialog.titleColor = config.titleColor or Talkies.titleColor or newDialog.messageColor
newDialog.messageBorderColor = config.messageBorderColor or Talkies.messageBorderColor or newDialog.messageBackgroundColor
newDialog.titleBorderColor = config.titleBorderColor or Talkies.titleBorderColor or newDialog.messageBorderColor
Talkies.dialogs:push(newDialog)
if Talkies.dialogs:len() == 1 then
Talkies.dialogs:peek():onstart()
end
return newDialog
end
function Talkies.update(dt)
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil then return end
local currentMessage = currentDialog.messages:peek()
if currentMessage.paused or currentMessage.complete then
Talkies.indicatorTimer = Talkies.indicatorTimer + (10 * dt)
if Talkies.indicatorTimer > Talkies.indicatorDelay then
Talkies.showIndicator = not Talkies.showIndicator
Talkies.indicatorTimer = 0
end
else
Talkies.showIndicator = false
end
if currentMessage:update(dt) then
if currentDialog.typedNotTalked then
playSound(currentDialog.talkSound)
elseif not currentDialog.talkSound:isPlaying() then
local pitch = currentDialog.pitchValues[math.random(#currentDialog.pitchValues)]
playSound(currentDialog.talkSound, pitch)
end
end
end
function Talkies.advanceMsg()
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil then return end
currentDialog:onmessage(currentDialog.messages:len() - 1)
if currentDialog.messages:len() == 1 then
Talkies.dialogs:pop()
currentDialog:oncomplete()
if Talkies.dialogs:len() == 0 then
Talkies.clearMessages()
else
Talkies.dialogs:peek():onstart()
end
end
currentDialog.messages:pop()
end
function Talkies.isOpen()
return Talkies.dialogs:peek() ~= nil
end
function Talkies.draw()
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil then return end
local currentMessage = currentDialog.messages:peek()
--love.graphics.push()
love.graphics.setDefaultFilter("nearest", "nearest")
local function getDimensions()
return core.screen:getDimensions()
end
local windowWidth, windowHeight = getDimensions()
love.graphics.setLineWidth(currentDialog.thickness)
-- message box
local boxW = windowWidth---(2*currentDialog.padding)
local boxH = (windowHeight/3)--(2*currentDialog.padding)
local boxX = 0--currentDialog.padding
local boxY = windowHeight-(boxH+currentDialog.padding)
-- image
local imgX, imgY, imgW, imgScale = boxX+currentDialog.padding, boxY+currentDialog.padding, 0, 0
if currentDialog.image ~= nil then
imgScale = (boxH - (currentDialog.padding * 2)) / currentDialog.image:getHeight()
imgW = currentDialog.image:getWidth() * imgScale
end
-- title box
local textX, textY = imgX + imgW + currentDialog.padding, boxY + 4
love.graphics.setFont(currentDialog.font)
if currentDialog.title ~= "" then
local titleBoxW = currentDialog.font:getWidth(currentDialog.title)+(4*currentDialog.padding)
local titleBoxH = currentDialog.fontHeight+currentDialog.padding
local titleBoxY = boxY-titleBoxH-(currentDialog.padding/2)
local titleBoxX = currentDialog.padding * 2
local titleX, titleY = titleBoxX + currentDialog.padding*2, titleBoxY + 2
-- Message title
love.graphics.setColor(currentDialog.titleBackgroundColor)
love.graphics.rectangle("fill", titleBoxX, titleBoxY, titleBoxW, titleBoxH, titleBoxH/2, titleBoxH/2)
if currentDialog.thickness > 0 then
love.graphics.setColor(currentDialog.titleBorderColor)
love.graphics.rectangle("line", boxX, titleBoxY, titleBoxW, titleBoxH, currentDialog.rounding, currentDialog.rounding)
end
love.graphics.setColor(currentDialog.titleColor)
love.graphics.print(currentDialog.title, titleX, titleY)
end
-- Main message box
love.graphics.setColor(currentDialog.messageBackgroundColor)
love.graphics.rectangle("fill", boxX, boxY, boxW, boxH, currentDialog.rounding, currentDialog.rounding)
if currentDialog.thickness > 0 then
love.graphics.setColor(currentDialog.messageBorderColor)
love.graphics.rectangle("line", boxX, boxY, boxW, boxH, currentDialog.rounding, currentDialog.rounding)
end
-- Message avatar
if currentDialog.image ~= nil then
love.graphics.push()
love.graphics.setColor(1, 1, 1)
love.graphics.draw(currentDialog.image, imgX, imgY, 0, imgScale, imgScale)
love.graphics.pop()
end
-- Message text
love.graphics.setColor(currentDialog.messageColor)
local textW = boxW - imgW - (4 * currentDialog.padding)
local _, modmsg = currentDialog.font:getWrap(currentMessage.msg, textW)
local catmsg = table.concat(modmsg, "\n")
local display = string.sub(catmsg, 1, #currentMessage.visible + #modmsg - 1)
love.graphics.print(display, textX, textY)
-- Message options (when shown)
if currentDialog:showOptions() and currentMessage.complete then
if currentDialog.inlineOptions then
local optionsY = textY+currentDialog.font:getHeight(currentMessage.visible)-(currentDialog.padding/1.6)
local optionLeftPad = currentDialog.font:getWidth(currentDialog.optionCharacter.." ")
for k, option in pairs(currentDialog.options) do
love.graphics.print(option[1], optionLeftPad+textX+currentDialog.padding, optionsY+((k-1)*currentDialog.fontHeight))
end
love.graphics.print(
currentDialog.optionCharacter.." ",
textX+currentDialog.padding,
optionsY+((currentDialog.optionIndex-1)*currentDialog.fontHeight))
else
local optionWidth = 0
local optionText = ""
for k, option in pairs(currentDialog.options) do
local newText = (currentDialog.optionIndex == k and currentDialog.optionCharacter or " ") .. " " .. option[1]
optionWidth = math.max(optionWidth, currentDialog.font:getWidth(newText) )
optionText = optionText .. newText .. "\n"
end
local optionsH = (currentDialog.font:getHeight() * #currentDialog.options)
local optionsX = math.floor((windowWidth / 2) - (optionWidth / 2))
local optionsY = math.floor((windowHeight / 3) - (optionsH / 2))
love.graphics.setColor(currentDialog.messageBackgroundColor)
love.graphics.rectangle("fill", optionsX - currentDialog.padding, optionsY - currentDialog.padding, optionWidth + currentDialog.padding * 2, optionsH + currentDialog.padding * 2, currentDialog.rounding, currentDialog.rounding)
if currentDialog.thickness > 0 then
love.graphics.setColor(currentDialog.messageBorderColor)
love.graphics.rectangle("line", optionsX - currentDialog.padding, optionsY - currentDialog.padding, optionWidth + currentDialog.padding * 2, optionsH + currentDialog.padding * 2, currentDialog.rounding, currentDialog.rounding)
end
love.graphics.setColor(currentDialog.messageColor)
love.graphics.print(optionText, optionsX, optionsY)
end
end
-- Next message/continue indicator
if Talkies.showIndicator then
love.graphics.print(currentDialog.indicatorCharacter, boxX+boxW-(4*currentDialog.padding), boxY+boxH-currentDialog.fontHeight)
end
--love.graphics.pop()
end
function Talkies.prevOption()
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil or not currentDialog:showOptions() then return end
currentDialog.optionIndex = currentDialog.optionIndex - 1
if currentDialog.optionIndex < 1 then currentDialog.optionIndex = #currentDialog.options end
playSound(currentDialog.optionSwitchSound)
end
function Talkies.nextOption()
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil or not currentDialog:showOptions() then return end
currentDialog.optionIndex = currentDialog.optionIndex + 1
if currentDialog.optionIndex > #currentDialog.options then currentDialog.optionIndex = 1 end
playSound(currentDialog.optionSwitchSound)
end
function Talkies.onAction()
local currentDialog = Talkies.dialogs:peek()
if currentDialog == nil then return end
local currentMessage = currentDialog.messages:peek()
if currentMessage.paused then currentMessage:resume()
elseif not currentMessage.complete then currentMessage:finish()
else
if currentDialog:showOptions() then
currentDialog.options[currentDialog.optionIndex][2]() -- Execute the selected function
playSound(currentDialog.optionOnSelectSound)
end
Talkies.advanceMsg()
end
end
function Talkies.clearMessages()
Talkies.dialogs = Fifo.new()
end
return Talkies

View File

@ -1,5 +1,5 @@
-- modules/assets/import :: a simple assets importer, to import easily everything into
-- the asset manager.
-- modules/assets :: a simple assets manager, aim to put every assets in a simple
-- serie of table in order to find them easily.
--[[
Copyright © 2019 Kazhnuz
@ -36,241 +36,262 @@ local Tileset = require(cwd .. "tileset")
local Autotile = require(cwd .. "autotile")
local Background = require(cwd .. "background")
local SFX = require(cwd .. "sfx")
local SFX = require(cwd .. "sfx")
-- INIT FUNCTIONS
-- Initilizing and configuring option
function Assets:new(isGlobal)
self.isGlobal = isGlobal
function Assets:new()
self:clear()
self.isActive = true
end
function Assets:clear()
if (self.isGlobal) then
core.assets:clear()
else
core.assets:clearLocal()
end
-- TODO: destroy individually each texture/image when assets are cleared
self:clearSprites()
self:clearSFX()
self:clearFonts()
self:resetMusic()
self:clearBackgrounds()
self:clearFonts()
self:clearTileset()
self:clearImages()
end
-- WRAPPER FUNCTIONS
-- Basic wrappers to ensure compatibility
function Assets:get(key)
return core.assets:get(key)
end
function Assets:getFont(key)
return self:getWithType(key, "font")
end
function Assets:getWithType(key, type)
return core.assets:get(key)
end
function Assets:draw(name, x, y, r, sx, sy, ox, oy, kx, ky)
core.assets:draw(name, x, y, r, sx, sy, ox, oy, kx, ky)
end
function Assets:newSFX(name, filepath)
self:addSFX(name, filepath)
end
function Assets:addImage(name, filename)
self:addTexture(name, filename)
end
function Assets:playSFX(name)
core.assets:playSFX(name)
end
function Assets:drawImage(name, x, y, r, sx, sy, ox, oy, kx, ky)
self:draw(name, x, y, r, sx, sy, ox, oy, kx, ky)
end
-- Music
function Assets:setMusic(filename, loop)
core.debug:logDebug("assets", "Assets:setMusic is deprecated")
core.music:addMusic(filename, true)
end
function Assets:silence()
core.debug:logDebug("assets", "Assets:silence is deprecated")
core.music:silence()
end
function Assets:resetMusic()
core.debug:logDebug("assets", "Assets:resetMusic is deprecated")
core.music:purge()
end
function Assets:playMusic()
core.debug:logDebug("assets", "Assets:playMusic is deprecated")
core.music:playMusic()
end
-- Activity
function Assets:setActivity(activity)
core.assets:setActivity(activity)
end
function Assets:switchActivity()
core.assets:switchActivity()
end
function Assets:getActivity()
core.assets:getActivity()
function Assets:update(dt)
if (self.isActive) then
self:animationsUpdate(dt)
end
end
-- IMPORT FUNCTIONS
-- Easilly import assets
function Assets:batchImport(datafile)
local datas = require(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
core.debug:logWarn("assets/importer", "unkown asset type " .. asset_type)
end
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
core.debug:warning("assets/importer", "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
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
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
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
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
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
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
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
for i, asset in ipairs(assets) do
self:addSFX(asset[1], asset[2])
end
end
-- ADD FUNCTIONS
-- Different wrapper to create easily asset objects
function Assets:add(name, assetObject)
core.assets:add(name, assetObject, self.isGlobal)
end
function Assets:addBackground(name, filepath)
self:add(name, Background(filepath))
end
function Assets:addSprite(name, filepath)
self:add(name, Sprite(filepath))
end
function Assets:addFont(key, filename, size)
self:add(key, Font(filename, size))
end
function Assets:addImageFont(key, filename, extraspacing)
self:add(key, ImageFont(filename, extraspacing))
end
function Assets:addTileset(name, filepath)
self:add(name, Tileset(filepath))
end
function Assets:addAutotile(name, tilesize)
self:add(name, Autotile(name, tilesize))
end
-- SFX & MUSICS
-- Handle sound effects and musics
function Assets:addSFX(name, filepath)
self:add(name, SFX(filepath))
self:newSFX(name, filepath)
end
function Assets:addTexture(name, filename)
self:add(name, Texture(filename))
end
-- DEPRECATED FUNCTIONS
-- Don't do anything and will be removed soon
function Assets:clearFonts()
core.debug:logDebug("assets", "Assets:clearFonts is deprecated")
end
function Assets:clearTileset()
core.debug:logDebug("assets", "Assets:clearTileset is deprecated")
end
function Assets:clearAutotile()
core.debug:logDebug("assets", "Assets:clearAutotile is deprecated")
function Assets:newSFX(name, filepath)
self.sfx[name] = SFX( filepath )
end
function Assets:clearSFX()
core.debug:logDebug("assets", "Assets:clearSFX is deprecated")
love.audio.stop( )
self.sfx = {}
end
function Assets:clearBackgrounds()
core.debug:logDebug("assets", "Assets:clearBackgrounds is deprecated")
function Assets:playSFX(filename)
if not (self.sfx[filename] == nil) then
self.sfx[filename]:play()
end
end
function Assets:clearSprites()
core.debug:logDebug("assets", "Assets:clearSprites is deprecated")
-- MUSIC FUNCTIONS
-- All thse functions are deprecated, prefer core.music
function Assets:setMusic(filename, loop)
core.debug:warning("assets", "Assets:setMusic is deprecated")
core.music:skip()
core.music:addMusic(filename, (loop ~= false))
core.music:playMusic()
end
function Assets:silence()
core.debug:warning("assets", "Assets:silence is deprecated")
core.music:silence()
end
function Assets:resetMusic()
core.debug:warning("assets", "Assets:resetMusic is deprecated")
core.music:purge()
end
function Assets:playMusic()
core.debug:warning("assets", "Assets:playMusic is deprecated")
core.music:playMusic()
end
-- IMAGES FUNCTIONS
-- Create directly texture items
function Assets:addImage(name, filename)
self.images[name] = Texture(filename)
end
function Assets:drawImage(name, x, y, r, sx, sy, ox, oy, kx, ky)
self.images[name]:draw(x, y, r, sx, sy, ox, oy, kx, ky)
end
function Assets:clearImages()
core.debug:logDebug("assets", "Assets:clearImages is deprecated")
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
-- SPRITES FUNCTIONS
-- Animated tileset
function Assets:addSprite(name, filepath)
self.sprites[name] = Sprite(filepath)
end
function Assets:animationsUpdate(dt)
for i,v in pairs(self.sprites) do
v:update(dt)
end
end
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
end
function Assets:addImageFont(key, filename, extraspacing)
local font = ImageFont(filename, extraspacing)
self.fonts[key] = font
end
function Assets:getFont(filename)
return self.fonts[filename]
end
function Assets:clearFonts()
self.fonts = {}
end
-- TILESET FUNCTIONS
-- Automatically create quads for a texture
function Assets:addTileset(name, filepath)
self.tileset[name] = Tileset(filepath)
end
function Assets:clearTileset()
self.tileset = {}
end
-- AUTOTILE FUNCTIONS
-- Automatically draw tiles
function Assets:addAutotile(name, tilesize)
self.autotile[name] = Autotile(name, tilesize)
end
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

View File

@ -35,6 +35,7 @@ function Animator:new(sprite)
self.animationData = {}
self.customSpeed = 0
self.speedFactor = 1
self:changeToDefaultAnimation()
end
@ -43,12 +44,16 @@ function Animator:setCustomSpeed(customSpeed)
self.customSpeed = customSpeed or 0
end
function Animator:setSpeedFactor(speedFactor)
self.speedFactor = speedFactor or 1
end
-- UPDATE FUNCTIONS
-- Update the animation of the animator
function Animator:update(dt)
if (self.currentAnimation == "") then
core.debug:logWarn("animator", "no current animation data")
core.debug:warning("animator", "no current animation data")
return 0
end
@ -56,16 +61,17 @@ function Animator:update(dt)
if (self.animationData.speed) == -1 then
speed = self.customSpeed --math.abs(self.xsp / 16)
end
self.frameTimer = self.frameTimer + (speed * dt)
self.frameTimer = self.frameTimer + (speed * dt * self.speedFactor)
if self.frameTimer > 1 then
self.frameTimer = 0
if self.frame == self.animationData.endAt then
self:sendCallback()
if not (self.animationData.pauseAtEnd) then
self.frame = self.animationData.loop
end
self:sendCallback()
else
self.frame = self.frame + 1
self:sendFrameSignal()
end
end
end
@ -124,17 +130,39 @@ end
-- CALLBACK FUNCTIONS
-- Handle getting a calback from the animation system
function Animator:setCallbackTarget(callbackTarget)
self.callbackTarget = callbackTarget
function Animator:setCallbackTarget(actor)
self.actor = actor
end
function Animator:setCallback(actor)
core.debug:warning("Animator", "Animator:setCallback is deprecated, prefer Animator:setCallbackTarget instead")
self:setCallbackTarget(actor)
end
function Animator:sendCallback()
if (self.callbackTarget ~= nil) then
if (self.callbackTarget.animationEnded ~= nil) then
self.callbackTarget:animationEnded(self.currentAnimation)
if (self.actor ~= nil) then
self.actor:animationEnded(self.currentAnimation)
end
end
function Animator:sendFrameSignal()
if (self.animationData.signal ~= nil) then
if (type(self.animationData.signal[1]) ~= "table") then
self:trySendSignal(self:getRelativeFrame(), self.animationData.signal)
else
for _, signal in ipairs(self.animationData.signal) do
self:trySendSignal(self:getRelativeFrame(), signal)
end
end
end
end
function Animator:trySendSignal(frame, signal)
if (signal[1] == frame) and (self.actor ~= nil) and (self.actor.receiveFrameSignal ~= nil) then
self.actor:receiveFrameSignal(signal[2])
end
end
-- DRAW FUNCTIONS
-- Draw animations using these functions

View File

@ -64,7 +64,6 @@ function Font:setAlign(align)
end
function Font:setFilter(filter)
local filter = filter or ""
self.filter = filter
end
@ -94,14 +93,6 @@ function Font:getColor()
return self.color
end
function Font:getFilter()
return self.filter
end
function Font:haveFilter()
return ((self.filter ~= "") and (self.filter ~= nil))
end
-- DRAW FUNCTIONS
-- print text using theses functions
@ -149,8 +140,8 @@ function Font:applyFilter(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
end
function Font:applyFilterShadow(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(0, 0, 0, 1)
self:printf(text, x+1, y+1, limit, align, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(0, 0, 0, self.color.a)
self:printf(text, x+1, y+1, limit, align, r, sx, sy, ox, oy, kx, ky)
utils.graphics.resetColor()
end

View File

@ -1,7 +1,7 @@
-- assets/sfx :: the sfx object, essentially used to be able to just have an sfx
-- assets/sfx :: the sfx object
--[[
Copyright © 2020 Kazhnuz
Copyright © 2021 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
@ -20,21 +20,31 @@
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 SFX = Object:extend()
-- INIT FUNCTIONS
-- Initilizing and configuring option
function SFX:new(filepath)
self.sound = love.audio.newSource(filepath, "static")
self.source = love.audio.newSource(filepath, "static")
end
-- INFO FUNCTIONS
-- get information with these functions
-- None for the moment
-- DRAW FUNCTIONS
-- Draw texture using these functions
function SFX:play()
self.sound:stop()
self.sound:setVolume(core.options.data.audio.sfx / 100)
love.audio.play(self.sound)
self.source:stop()
self.source:setVolume(core.options.data.audio.sfx / 100)
love.audio.play(self.source)
end
function SFX:stop()
self.source:stop()
end
return SFX

View File

@ -63,6 +63,10 @@ function Sprite:changeAnimation(name, restart)
self.animator:changeAnimation(name, restart)
end
function Sprite:setSpeedFactor(speedFactor)
self.animator:setSpeedFactor(speedFactor)
end
-- INFO FUNCTIONS
-- get information with these functions
@ -93,11 +97,11 @@ end
-- DRAW FUNCTIONS
-- Draw sprites using these functions
function Sprite:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
function Sprite:draw(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)
function Sprite:drawMask(x, y, r, sx, sy, ox, oy, kx, ky)
self.animator:drawMask(x, y, r, sx, sy, ox, oy, kx, ky)
end
@ -118,7 +122,7 @@ function Sprite:drawPart(x, y, w, h, r, sx, sy, ox, oy, kx, ky)
end
love.graphics.setScissor(x - ox, y - oy, w, h)
self:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
self:draw(x, y, r, sx, sy, ox, oy, kx, ky)
love.graphics.setScissor( )
end

View File

@ -1,144 +0,0 @@
-- game :: The main game subsystem. Basically a big object that handle all the
-- game-related data like characters, monsters, etc. While the core aim to be
-- reusable at will, the game is specifically made for the current game.
-- It's also what handle the savedata for games
--[[
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 GameSystem = Object:extend()
local binser = require("birb.libs.binser")
local DEFAULT_SAVENUMBER = 3
function GameSystem:new()
self.currentSlot = -1
self.submodules = {}
self.playtime = 0
self:register()
end
function GameSystem:register()
core:registerGameSystem(self)
end
function GameSystem:registerSubmodules(submodule)
local name = submodule.name
self.submodules[name] = submodule
end
-- UPDATE FUNCTIONS
-- Update every submodules
function GameSystem:update(dt)
self.playtime = self.playtime + dt
for k, submodule in pairs(self.submodules) do
submodule:update(dt)
end
end
-- DATA MANAGEMENT FUNCTIONS
-- Get and set data in the gamesystem object
function GameSystem:setData(data)
local data = data
self.playtime = data.playtime
for k, submodule in pairs(self.submodules) do
submodule:setData(data[k])
end
end
function GameSystem:getData()
local data = {}
data.playtime = self.playtime
for k, submodule in pairs(self.submodules) do
data[k] = submodule:getData()
end
return data
end
-- SAVE MANAGEMENT FUNCTIONS
-- Get and set data in the gamesystem object
function GameSystem:getSaveNumber()
return DEFAULT_SAVENUMBER
end
function GameSystem:resetSave(saveid)
if utils.filesystem.exists("save" .. saveid .. ".save") then
love.filesystem.remove( "save" .. saveid .. ".save" )
end
end
function GameSystem:resetAllSaves()
for i=1, self:getSaveNumber() do
self:resetSave(i)
end
end
function GameSystem:getSavePath(saveid, absolute)
local saveDir = ""
if (absolute) then
saveDir = love.filesystem.getSaveDirectory() .. "/"
if not utils.filesystem.exists(saveDir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = saveDir .. self:getSaveName(saveid)
return filepath
end
function GameSystem:getSaveName(saveid)
return "save" .. saveid .. ".save"
end
function GameSystem:saveFileExist(saveid)
return utils.filesystem.exists(self:getSaveName(saveid))
end
function GameSystem:read(saveid)
self.currentSlot = saveid or self.currentSlot
if (self.currentSlot > 0) then
local savepath = self:getSavePath(self.currentSlot, true)
if self:saveFileExist(self.currentSlot) then
local datas = binser.readFile(savepath)
self:setData(datas[1])
end
end
end
function GameSystem:write()
if (self.currentSlot > 0) then
local data = self:getData()
local savepath = self:getSavePath(self.currentSlot, true)
binser.writeFile(savepath, data)
end
end
return GameSystem

View File

@ -1,23 +0,0 @@
local SubModule = Object:extend()
function SubModule:new(game, name)
self.name = name or error("SUBMODULE must have a name")
self.game = game
self.data = {}
self:register()
end
function SubModule:register()
self.game:registerSubmodules(self)
end
function SubModule:getData()
return self.data
end
function SubModule:setData(data)
self.data = data
end
return SubModule

View File

@ -0,0 +1,16 @@
local Parent = require "birb.modules.gui.elements.drawable"
local AssetElement = Parent:extend()
function AssetElement:new(name, assetType, assetName, x, y,r,sx,sy,ox,oy, opacity)
self:initWrapper()
local asset = self.assets[assetType][assetName]
assert(asset ~= nil, assetName .. ' (' .. assetType .. ") doesn't exist")
AssetElement.super.new(self, name, asset, x, y,r,sx,sy,ox,oy, opacity)
end
function AssetElement:draw()
self.drawable:draw(self.x,self.y,self.r,self.sx,self.sy,self.ox,self.oy)
end
return AssetElement

View File

@ -0,0 +1,96 @@
local Parent = require "birb.modules.gui.elements.parent"
local CanvasElement = Parent:extend()
function CanvasElement:new(name, x, y, w, h, r,sx,sy,ox,oy, opacity)
self:initCanvas()
CanvasElement.super.new(self, name, x, y, w, h)
self.r = r or 0
self.sx, self.sy = sx or 1, sy or 1
self.ox, self.oy = self:parseOrigin(ox, w), self:parseOrigin(oy, h)
self.opacity = opacity or 1
end
function CanvasElement:initCanvas()
self.canvas = {}
self.canvas.needRedraw = true
self.canvas.texture = nil
self.canvas.isAnimated = false
self.canvas.padding = 8
self.canvas.final = nil
self.canvas.dual = false
end
function CanvasElement:updateElement(dt)
CanvasElement.super.updateElement(self, dt)
end
function CanvasElement:getCanvasDimensions()
return self:getDimensions()
end
function CanvasElement:redraw()
if (self.canvas.needRedraw or self.canvas.isAnimated) then
self:generateTexture()
end
if (self.canvas.dual) then
local w, h = self:getDimensions()
local canvas = love.graphics.newCanvas(w + (self.canvas.padding*2), h + (self.canvas.padding*2))
love.graphics.setCanvas(canvas)
love.graphics.draw(self.canvas.texture, 0, 0)
self:drawFinalTexture()
self.canvas.final = canvas
love.graphics.setCanvas()
end
end
function CanvasElement:generateTexture()
local w, h = self:getDimensions()
local canvas = love.graphics.newCanvas(w + (self.canvas.padding*2), h + (self.canvas.padding*2))
love.graphics.setCanvas(canvas)
self:drawTexture()
self.canvas.needRedraw = false
love.graphics.setCanvas()
if (self.canvas.isAnimated) then
self.canvas.texture = canvas
else
local imageData = canvas:newImageData()
self.canvas.texture = love.graphics.newImage(imageData)
canvas:release()
imageData:release()
end
end
function CanvasElement:drawTexture()
end
function CanvasElement:drawFinalTexture()
end
function CanvasElement:parseOrigin(origin, size)
if (origin == "center") then
return size/2
elseif (origin == "end") then
return size
else
return origin or 0
end
end
function CanvasElement:draw()
love.graphics.setColor(1, 1, 1, self.opacity)
local texture = self.canvas.texture
if (self.canvas.dual) then
texture = self.canvas.final
end
love.graphics.draw(texture, self.x - self.canvas.padding,self.y - self.canvas.padding,self.r,self.sx,self.sy,self.ox,self.oy)
love.graphics.setColor(1, 1, 1, 1)
end
return CanvasElement

View File

@ -0,0 +1,19 @@
local Parent = require "birb.modules.gui.elements.parent"
local ColorElement = Parent:extend()
function ColorElement:new(name, r, g, b, opacity)
local w, h = core.screen:getDimensions()
ColorElement.super.new(self, name, self.x, self.y, w, h)
self.r = r or 1
self.g = g or 1
self.b = b or 1
self.opacity = opacity or 1
end
function ColorElement:draw()
love.graphics.setColor(self.r, self.g, self.b, self.opacity)
love.graphics.rectangle("fill", 0, 0, self.w, self.h)
utils.graphics.resetColor()
end
return ColorElement

View File

@ -0,0 +1,35 @@
local Parent = require "birb.modules.gui.elements.parent"
local CompositeElement = Parent:extend()
function CompositeElement:new(name, x, y, childrenList)
self.children = {}
self:addChildren(childrenList)
CompositeElement.super.new(self, name, x, y, 1, 1)
self.isVisible = true
end
function CompositeElement:addChildren(list)
for _, childData in ipairs(list) do
self:addChild(childData[1], childData[2], childData[3])
end
end
function CompositeElement:addChild(children, relx, rely)
local child = {}
child.name = children.name
child.relx = relx
child.rely = rely
table.insert(self.children, child)
end
function CompositeElement:update(dt)
for _, child in ipairs(self.children) do
local childElement = self.gui.elements[child.name]
childElement.x = self.x + child.relx
childElement.y = self.y + child.rely
childElement.isVisible = self.isVisible
childElement.depth = self.depth
end
end
return CompositeElement

View File

@ -0,0 +1,13 @@
local Parent = require "birb.modules.gui.elements.variable"
local CounterElement = Parent:extend()
function CounterElement:new(name, fontName, object, varName, nbrs, x, y, align)
CounterElement.super.new(self, name, fontName, object, varName, x, y, align)
self.nbrs = nbrs or 0
end
function CounterElement:getText()
return utils.math.numberToString(CounterElement.super.getText(self), self.nbrs)
end
return CounterElement

View File

@ -0,0 +1,31 @@
local Parent = require "birb.modules.gui.elements.parent"
local DrawableElement = Parent:extend()
function DrawableElement:new(name, drawable, x, y,r,sx,sy,ox,oy, opacity)
self.drawable = drawable
local w, h = self.drawable:getDimensions()
DrawableElement.super.new(self, name, x, y, w, h)
self.r = r or 0
self.sx, self.sy = sx or 1, sy or 1
self.ox, self.oy = self:parseOrigin(ox, w), self:parseOrigin(oy, h)
self.opacity = opacity or 1
end
function DrawableElement:parseOrigin(origin, size)
if (origin == "center") then
return size/2
elseif (origin == "end") then
return size
else
return origin or 0
end
end
function DrawableElement:draw()
love.graphics.setColor(1, 1, 1, self.opacity)
love.graphics.draw(self.drawable, self.x,self.y,self.r,self.sx,self.sy,self.ox,self.oy)
love.graphics.setColor(1, 1, 1, 1)
end
return DrawableElement

View File

@ -0,0 +1,157 @@
local Rect = require "birb.classes.2D.rect"
local GuiElement = Rect:extend()
local TweenManager = require "birb.classes.time"
function GuiElement:new(name, x, y, w, h)
GuiElement.super.new(self, x, y, w, h)
self.name = name
self.isVisible = true
self.screen = nil
self.depth = 10
self.tweens = TweenManager(self)
self:initWrapper()
self:register()
end
function GuiElement:initWrapper()
self.scene = core.scenemanager.nextScene or core.scenemanager.currentScene
self.gui = self.scene.gui
self.assets = self.scene.assets
end
function GuiElement:setKeyPressAction(func)
self.func = func
end
function GuiElement:register()
self.creationId = self.gui:addElement(self.name, self)
end
function GuiElement:destroy()
self.gui:deleteElement(self.name)
if (self.screen ~= nil) then
self.screen:deleteElement(self.name)
end
end
-- VISIBILITY/ACTIVITY
-- Handle drawing and how we interact with
function GuiElement:setDepth(depth)
self.depth = depth or 0
end
function GuiElement:getVisibility()
if (self.screen ~= nil) then
return (self.isVisible and self.screen.isVisible)
else
return self.isVisible
end
end
function GuiElement:setVisibility(visibility)
self.isVisible = visibility
end
function GuiElement:getFocus(widgetId, page)
self.gui:setFocus(self.name, widgetId, page)
end
function GuiElement:haveFocus()
return (self.gui.focusedElement == self.name)
end
function GuiElement:looseFocus()
if (self:haveFocus()) then
self.gui:removeFocus()
end
end
function GuiElement:setSubFocus()
-- Useless for basic element
end
function GuiElement:isTransforming()
return self.tweens:haveTween()
end
-- UPDATE FUNCTIONS
-- Update the menu every game update
-- External update function
function GuiElement:updateElement(dt)
self:update(dt)
self.tweens:update(dt)
end
-- Internal update function
function GuiElement:update(dt)
-- Cette fonction ne contient rien par défaut
end
-- TWEEN FUNCTIONS
-- Handle tweening
function GuiElement:newTween(start, duration, target, easing)
self.tweens:newTween(start, duration, target, easing)
end
function GuiElement:newMovement(start, duration, x, y, easing)
self:newTween(start, duration, {x = x, y = y}, easing)
end
function GuiElement:newSwitch(start, bools)
self.tweens:newSwitch(start, bools)
end
function GuiElement:newFunc(start, name, func)
self.tweens:newFunc(start, name, func)
end
function GuiElement:delayFocus(start)
self.tweens:newFunc(start, "focus", function ()
self:getFocus()
end)
end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function GuiElement:redraw()
end
function GuiElement:drawElement()
self:draw()
end
function GuiElement:draw()
-- nothing here
end
-- KEYBOARD FUNCTIONS
-- Handle key press
function GuiElement:keypressed(key)
if (self.func ~= nil) then
self.func(key)
end
end
-- MOUSE FUNCTIONS
-- Handle pointers (clic/touch)
function GuiElement:mousemoved(x, y)
-- Cette fonction ne contient rien par défaut
end
function GuiElement:mousepressed(x, y, button, istouch)
-- Cette fonction ne contient rien par défaut
end
return GuiElement

View File

@ -0,0 +1,23 @@
local Parent = require "birb.modules.gui.elements.parent"
local TextElement = Parent:extend()
function TextElement:new(name, fontName, text, x, y, align)
TextElement.super.new(self, name, x, y, 1, 1)
self.text = text
self.font = self.assets.fonts[fontName]
self.align = align
self.opacity = 1
end
function TextElement:getText()
return self.text
end
function TextElement:draw()
self.font:setColor(1, 1, 1, self.opacity)
self.font:draw(self:getText(), self.x, self.y, -1, self.align)
love.graphics.setColor(1, 1, 1, 1)
end
return TextElement

View File

@ -0,0 +1,19 @@
local Parent = require "birb.modules.gui.elements.drawable"
local TileElement = Parent:extend()
function TileElement:new(name, assetName, id, x, y,r,sx,sy,ox,oy, opacity)
self:initWrapper()
local asset = self.assets.tileset[assetName]
assert(asset ~= nil, assetName .. " ( tileset ) doesn't exist")
self.tileId = id
TileElement.super.new(self, name, asset, x, y,r,sx,sy,ox,oy, opacity)
end
function TileElement:draw()
love.graphics.setColor(1, 1, 1, self.opacity)
self.drawable:drawTile(self.tileId, self.x,self.y,self.r,self.sx,self.sy,self.ox,self.oy)
utils.graphics.resetColor()
end
return TileElement

View File

@ -0,0 +1,14 @@
local Parent = require "birb.modules.gui.elements.text"
local VariableElement = Parent:extend()
function VariableElement:new(name, fontName, object, varName, x, y, align)
VariableElement.super.new(self, name, fontName, "", x, y, align)
self.object = object
self.variable = varName
end
function VariableElement:getText()
return self.object[self.variable]
end
return VariableElement

187
birb/modules/gui/init.lua Normal file
View File

@ -0,0 +1,187 @@
-- modules/gui :: a simple gui system, that manage all overhead elements of the game.
--[[
Copyright © 2021 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 Gui = Object:extend()
local ElementList = require "birb.modules.gui.mixins.elements"
local ScreenList = require "birb.modules.gui.mixins.screens"
Gui:implement(ScreenList)
Gui:implement(ElementList)
local TransformDataStruct = require "birb.structures.tween"
function Gui:new(scene)
self.scene = scene
self:reset()
self.sfx = {}
end
function Gui:reset()
self:initElements()
self:initScreens()
end
function Gui:update(dt)
for _, element in pairs(self.elements) do
if (element ~= nil) then
element:updateElement(dt)
end
end
for _, screen in pairs(self.screens) do
if (screen ~= nil) then
screen:update(dt)
end
end
end
-- TWEEN FUNCTIONS
-- Handle tweening
function Gui:transform(data, delay)
delay = delay or 0
local time = 0
for _, rawTransform in ipairs(data) do
local newTime = self:transformOne(rawTransform, delay)
time = math.max(time, newTime)
end
return time
end
function Gui:playScreenTransform(screenname, name, delay)
return self.screens[screenname]:playTransform(name, delay)
end
function Gui:transformOne(rawTransform, delay)
delay = delay or 0
local struct = TransformDataStruct[rawTransform[2]]
assert(struct ~= nil, "Structure " .. rawTransform[1] .. " doesn't exists ")
local transform = utils.table.parse(rawTransform, struct, 0)
local time = transform.start + delay
if transform.type == "tween" then
self:newTween(transform.name, time, transform.duration, transform.target, transform.easing)
elseif transform.type == "movement" then
self:newMovement(transform.name, time, transform.duration, transform.x, transform.y, transform.easing)
elseif transform.type == "switch" then
self:newSwitch(transform.name, time, transform.bools)
elseif transform.type == "delayFocus" then
self:delayFocus(transform.name, time)
end
if (transform.duration ~= nil) then
time = time + transform.duration
end
return time
end
function Gui:newTween(element, start, duration, target, easing)
assert(self.elements[element] ~= nil, element .. " does not exists")
self.elements[element]:newTween(start, duration, target, easing)
end
function Gui:newMovement(element, start, duration, x, y, easing)
assert(self.elements[element] ~= nil, element .. " does not exists")
self.elements[element]:newMovement(start, duration, x, y, easing)
end
function Gui:newSwitch(element, start, bools)
assert(self.elements[element] ~= nil, element .. " does not exists")
self.elements[element]:newSwitch(start, bools)
end
function Gui:delayFocus(element, start)
assert(self.elements[element] ~= nil, element .. " does not exists")
self.elements[element]:delayFocus(start)
end
function Gui:hideScreen(screenname)
self.screens[screenname]:hide()
end
function Gui:showScreen(screenname, focusElement, widgetId, page, arbitraryDatas)
self.screens[screenname]:show(focusElement, widgetId, page)
self.screens[screenname]:setDatas(arbitraryDatas)
end
-- SOUND FUNCTIONS
-- Handle SFX
function Gui:addSFX(guiName, sfxName)
self.sfx[guiName] = sfxName
end
function Gui:playSFX(type)
local sfxName = self.sfx[type]
if (sfxName ~= nil) then
local sfx = self.scene.assets.sfx[sfxName]
if (sfx ~= nil) then
sfx:play()
end
end
end
-- KEYBOARD FUNCTIONS
-- Handle keyboard
function Gui:keycheck(keys)
local haveFocus = self:haveFocus()
if (haveFocus) then
local elem = self:getFocusedElement()
for key,_ in pairs(keys) do
if (keys[key].isPressed and not elem:isTransforming()) then
elem:keypressed(key)
end
end
end
return haveFocus
end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function Gui:redraw()
for _, element in pairs(self:getVisibleElement(true)) do
element:redraw()
end
for _, element in pairs(self:getVisibleElement(false)) do
element:redraw()
end
end
function Gui:drawTop()
for _, element in ipairs(self:getVisibleElement(true)) do
element:drawElement()
end
end
function Gui:drawBottom()
for _, element in ipairs(self:getVisibleElement(false)) do
element:drawElement()
end
end
return Gui

View File

@ -31,9 +31,9 @@ local View2D = require(cwd .. "views.view2D")
-- INIT FUNCTIONS
-- Initialize and configure the flowbox
function FlowBox:new(menusystem, name, x, y, w, h, slots_hor, slots_vert)
function FlowBox:new(name, x, y, w, h, slots_hor, slots_vert)
self.view = View2D(slots_hor, slots_vert)
FlowBox.super.new(self, menusystem, name, x, y, w, h)
FlowBox.super.new(self, name, x, y, w, h)
self:setRealSize()
end
@ -85,18 +85,22 @@ function FlowBox:moveByKeys(key)
local col, line = self.view:getCoord(self.widget:getSelected())
if key == 'left' then
self:moveCursor2D(col - 1, line)
self:playNavigationSound()
end
if key == 'right' then
self:moveCursor2D(col + 1, line)
self:playNavigationSound()
end
if key == 'up' then
self:moveCursor2D(col, line - 1)
self:playNavigationSound()
end
if key == 'down' then
self:moveCursor2D(col, line + 1)
self:playNavigationSound()
end
end
@ -112,10 +116,10 @@ end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function FlowBox:draw()
function FlowBox:drawTexture()
self.view:updateFirstSlot(self.widget:getSelected())
local widgety = self.y
local widgetx = self.x
local widgety = self.canvas.padding
local widgetx = self.canvas.padding
local listWidget = self.widget:getList(self.view.firstSlot, self.view.slotNumber)
@ -138,7 +142,7 @@ function FlowBox:getGraphicalCursorPosition()
local col, line = self.view:getCoord(self.widget:getSelected())
local x = (col) * h
local y = (line - beginline) * h
return self.x + x, self.y + y, w, h
return x, y, w, h
end
return FlowBox

View File

@ -26,14 +26,14 @@ local cwd = (...):gsub('%.grid$', '') .. "."
local Menu = require(cwd .. "parent")
local GridBox = Menu:extend()
local View2D = require "birb.modules.menusystem.menus.views.view2D"
local View2D = require "birb.modules.gui.menus.views.view2D"
-- INIT FUNCTIONS
-- Initialize and configure the menu
function GridBox:new(menusystem, name, x, y, w, h, colNumber, lineNumber)
function GridBox:new(name, x, y, w, h, colNumber, lineNumber)
self.view = View2D(colNumber, lineNumber)
GridBox.super.new(self, menusystem, name, x, y, w, h)
GridBox.super.new(self, name, x, y, w, h)
self.h = lineNumber * self.widgetSize.h -- On fait en sorte que la hauteur
self.w = colNumber * self.widgetSize.w -- et la largeur
-- soit un multiple du nombre de slot et de leur dimensions
@ -150,18 +150,22 @@ function GridBox:keyreleased(key, code)
local col, line = self.cursor.x, self.cursor.y
if key == 'left' then
self:moveCol(-1)
self:playNavigationSound()
end
if key == 'right' then
self:moveCol(1)
self:playNavigationSound()
end
if key == 'up' then
self:moveLine(-1)
self:playNavigationSound()
end
if key == 'down' then
self:moveLine(1)
self:playNavigationSound()
end
if key == "A" and self.widget:getSelected() <= self.widget:lenght() then
@ -247,12 +251,12 @@ end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function GridBox:draw()
function GridBox:drawTexture()
for i,v in ipairs(self.slots) do
if self:haveWidget(i) then
local widgetx = self.x + (v.x * self.widgetSize.w)
local widgety = self.y + (v.y * self.widgetSize.h)
local widgetx = self.canvas.padding + (v.x * self.widgetSize.w)
local widgety = self.canvas.padding + (v.y * self.widgetSize.h)
if self.widget:getSelected() == v.widgetID and self:haveFocus() == true then
self.widget.list[v.widgetID]:drawSelected(widgetx, widgety)
else
@ -269,7 +273,7 @@ function GridBox:drawCursor()
local w, h = self:getWidgetSize(slot)
local x = self.slots[slot].x * self.widgetSize.w
local y = self.slots[slot].y * self.widgetSize.h
self:drawGraphicalCursor(self.x + x, self.y + y, w, h)
self:drawGraphicalCursor(x, y, w, h)
end
end

View File

@ -31,9 +31,9 @@ local View1D = require(cwd .. "views.view1D")
-- INIT FUNCTIONS
-- Initialize and configure functions.
function HListBox:new(menusystem, name, x, y, w, h, slotNumber)
function HListBox:new(name, x, y, w, h, slotNumber)
self.view = View1D(slotNumber)
HListBox.super.new(self, menusystem, name, x, y, w, h)
HListBox.super.new(self, name, x, y, w, h)
self.w = slotNumber * self.widgetSize.w -- On fait en sorte que la hauteur
-- soit un multiple du nombre de slot et de leur hauteur
end
@ -60,10 +60,14 @@ end
function HListBox:moveByKeys(key, code)
if key == 'left' then
self.widget:moveCursor(-1)
self.canvas.needRedraw = true
self:playNavigationSound()
end
if key == 'right' then
self.widget:moveCursor(1)
self.canvas.needRedraw = true
self:playNavigationSound()
end
end
@ -77,15 +81,15 @@ end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function HListBox:draw()
function HListBox:drawTexture()
self.view:updateFirstSlot(self.widget:getSelected())
local widgetx = self.x
local widgetx = self.canvas.padding
local listWidget = self.widget:getList(self.view.firstSlot, self.view.slotNumber)
for _, widget in ipairs(listWidget) do
widget:drawWidget(widgetx, self.y, self.widgetSize.w, self.h)
widget:drawWidget(widgetx, self.canvas.padding, self.widgetSize.w, self.h)
widgetx = widgetx + self.widgetSize.w
end
end
@ -95,7 +99,7 @@ function HListBox:getGraphicalCursorPosition()
local w, h = self:getWidgetSize()
local x = (self.widget:getSelected() - self.view.firstSlot) * w
return self.x + x,self.y, w, h
return x, 0, w, h
end
return HListBox

View File

@ -31,11 +31,13 @@ local View1D = require(cwd .. "views.view1D")
-- INIT FUNCTIONS
-- Initialize and configure functions.
function ListBox:new(menusystem, name, x, y, w, h, slotNumber)
function ListBox:new(name, x, y, w, h, slotNumber)
self.view = View1D(slotNumber)
ListBox.super.new(self, menusystem, name, x, y, w, h)
ListBox.super.new(self, name, x, y, w, h)
self.h = slotNumber * self.widgetSize.h -- On fait en sorte que la hauteur
-- soit un multiple du nombre de slot et de leur hauteur
self.lateralFunc = nil
self.packAtEnd = false
end
-- UPDATE FUNCTIONS
@ -54,16 +56,28 @@ function ListBox:resetView()
self.view:reset()
end
function ListBox:addLateralAction(func)
self.lateralFunc = func
end
-- KEYBOARD FUNCTIONS
-- Handle input from keyboard/controllers.
function ListBox:moveByKeys(key)
if key == 'up' then
self.widget:moveCursor(-1)
self:playNavigationSound()
self.canvas.needRedraw = true
end
if key == 'down' then
self.widget:moveCursor(1)
self:playNavigationSound()
self.canvas.needRedraw = true
end
if (self.lateralFunc ~= nil and (key == 'left' or key == 'right')) then
self.widget:lateralAction(self.lateralFunc, key)
end
end
@ -77,24 +91,26 @@ end
-- DRAW FUNCTIONS
-- draw the menu and the rest of content.
function ListBox:draw()
self.view:updateFirstSlot(self.widget:getSelected())
local widgety = self.y
function ListBox:getListPart(relativeNumber)
if (self.packAtEnd) then relativeNumber = relativeNumber + math.max(0, self.view.slotNumber - self.widget:lenght()) end
return 0, (relativeNumber) * self.widgetSize.h, self.w, self.widgetSize.h
end
function ListBox:drawTexture()
self.view:updateFirstSlot(self.widget:getSelected())
local listWidget = self.widget:getList(self.view.firstSlot, self.view.slotNumber)
for _, widget in ipairs(listWidget) do
widget:drawWidget(self.x, widgety, self.w, self.widgetSize.h)
widgety = widgety + self.widgetSize.h
for i, widget in ipairs(listWidget) do
local x, y, w, h = self:getListPart(i - 1)
self:drawWidgetBackground(x + self.canvas.padding, y + self.canvas.padding, w, h)
widget:drawWidget(x + self.canvas.padding, y + self.canvas.padding, w, h)
end
end
function ListBox:getGraphicalCursorPosition()
self.view:updateFirstSlot(self.widget:getSelected())
local w, h = self:getWidgetSize()
local y = (self.widget:getSelected() - self.view.firstSlot) * h
local x, y, w, h = self:getListPart(self.widget:getSelected() - self.view.firstSlot)
return self.x,self.y + y, w, h
return self:getListPart(self.widget:getSelected() - self.view.firstSlot)
end
return ListBox

View File

@ -1,5 +1,5 @@
local MenuModel = Object:extend()
local Page = require "birb.modules.menusystem.menus.model.page"
local Page = require "birb.modules.gui.menus.model.page"
local function updateWidgetByOrder(a, b)
if a.order ~= b.order then
@ -21,6 +21,7 @@ function MenuModel:new()
self.cancel = 0
self.limit = -1
-- self:updateWidgetSize()
self.hoverFunc = nil
end
function MenuModel:clear()
@ -33,6 +34,7 @@ end
function MenuModel:addPage(pageName)
local page = Page()
page.name = pageName
self.pages[pageName] = page
self.currentPage = pageName
return page
@ -58,6 +60,7 @@ end
function MenuModel:switch(pageName)
if (self:pageExists(pageName)) then
self.currentPage = pageName
self:hoverAction()
end
end
@ -70,6 +73,10 @@ function MenuModel:getCurrentPage()
return self:getPage(self.currentPage)
end
function MenuModel:getCurrentPageName()
return self.currentPage
end
-- UPDATE/DRAW FUNCTIONS
-- All the update functions
@ -91,6 +98,10 @@ end
-- ACTION FUNCTIONS
-- All the actions callback used by the widgets
function MenuModel:addHoverAction(func)
self.hoverFunc = func
end
function MenuModel:cancelAction()
local page = self:getCurrentPage()
page:cancelAction()
@ -106,6 +117,19 @@ function MenuModel:selectedAction()
page:selectedAction()
end
function MenuModel:hoverAction()
local page = self:getCurrentPage()
if (self.hoverFunc ~= nil) then
page:hoverAction(self.hoverFunc)
end
end
function MenuModel:lateralAction(func, key)
local page = self:getCurrentPage()
page:lateralAction(func, key)
end
-- WIDGET FUNCTIONS
-- All the functions to handle widgets
@ -142,9 +166,9 @@ end
-- CANCEL FUNCTIONS
-- Add a widget as a "cancel" function
function MenuModel:setCancelWidget()
function MenuModel:setCancelWidget(id)
local page = self:getCurrentPage()
page:setCancelWidget()
page:setCancelWidget(id)
end
function MenuModel:getCancelWidget()
@ -167,22 +191,36 @@ end
function MenuModel:trySelectWidget(cursorid)
local page = self:getCurrentPage()
return page:trySelectWidget(cursorid)
local isSuccess = page:trySelectWidget(cursorid)
if (isSuccess) then
self:hoverAction()
end
return isSuccess
end
function MenuModel:setCursor(cursorid)
local page = self:getCurrentPage()
page:setCursor(cursorid)
self:hoverAction()
end
function MenuModel:moveCursorAbsolute(newSelected)
local page = self:getCurrentPage()
page:moveCursorAbsolute(newSelected)
self:hoverAction()
end
function MenuModel:moveCursor(relative)
local page = self:getCurrentPage()
page:moveCursor(relative)
self:hoverAction()
end
-- DRAW
-- Draw widget
function MenuModel:redraw()
local page = self:getCurrentPage()
page:redraw()
end

View File

@ -66,14 +66,15 @@ end
-- All the actions callback used by the widgets
function Page:cancelAction()
if (self.cancel ~= 0) then
self:action(self.cancel, "key")
if (self:getCancelWidget() ~= 0) then
self:action(self:getCancelWidget(), "key")
end
end
function Page:action(id, type)
if (self:widgetExist(id)) then
self.widgets[id]:action(type)
self.widgets[id]:playSFX()
end
end
@ -83,6 +84,18 @@ function Page:selectedAction()
end
end
function Page:hoverAction(func)
if (self:widgetExist(self.selected)) then
func(self.widgets[self.selected])
end
end
function Page:lateralAction(func, key)
if (self:widgetExist(self.selected)) then
func(key, self.widgets[self.selected], self.selected, self.name)
end
end
-- WIDGET FUNCTIONS
-- All the functions to handle widgets
@ -133,11 +146,18 @@ end
-- Add a widget as a "cancel" function
function Page:setCancelWidget(id)
self.cancel = #self.widgets
if (id == nil) then
id = #self.widgets
end
self.cancel = id
end
function Page:getCancelWidget(id)
return self.cancel
function Page:getCancelWidget()
if (self.cancel == "last") then
return #self.widgets
else
return self.cancel
end
end
-- CURSOR FUNCTIONS
@ -181,5 +201,11 @@ function Page:moveCursor(relative)
self:moveCursorAbsolute(self.selected + relative)
end
-- DRAW
function Page:redraw()
for _, widget in ipairs(self.widgets) do
widget:redraw()
end
end
return Page

View File

@ -21,30 +21,46 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local GuiElement = require "birb.modules.menusystem.parent"
local GuiElement = require "birb.modules.gui.elements.canvas"
local Menu = GuiElement:extend()
local MenuModel = require "birb.modules.menusystem.menus.model"
local MenuModel = require "birb.modules.gui.menus.model"
local menuUtils = require "birb.modules.gui.utils"
local menuUtils = require "birb.modules.menusystem.utils"
-- INIT FUNCTIONS
-- Initialize and configure functions.
function Menu:new(menusystem, name, x, y, w, h)
function Menu:new(name, x, y, w, h)
Menu.super.new(self, name, x, y, w, h)
self.menusystem = self:getGui()
--TODO: remove this
self.menusystem = self.gui
self.widget = MenuModel()
self.widgetSize = {}
self:updateWidgetSize()
self:resetSound()
self:initCanvas()
self.cancelFunc = nil
self.canvas.dual = true
end
-- FUNCTIONS FUNCTIONS
-- Add functions to the menu system
function Menu:addCancelAction(func)
self.cancelFunc = func
end
function Menu:addHoverAction(func)
self.widget:addHoverAction(func)
end
-- INTERACTION FUNCTIONS
-- Keyboard and mouse
function Menu:keyreleased(key)
function Menu:keypressed(key)
self:moveByKeys(key)
self:actionAndCancel(key)
end
@ -77,10 +93,12 @@ end
function Menu:actionAndCancel(key)
if key == "A" then
self.widget:selectedAction()
self.canvas.needRedraw = true
end
if key == "B" then
self.widget:cancelAction()
self:cancelAction()
self.canvas.needRedraw = true
end
end
@ -107,21 +125,38 @@ function Menu:getPage(pageName)
self.widget:getPage(pageName)
end
function Menu:getCurrentPageName()
return self.widget:getCurrentPageName()
end
function Menu:switch(pageName)
self.widget:switch(pageName)
self:resetView()
self.canvas.needRedraw = true
end
function Menu:back()
self.widget:back()
self:resetView()
self.canvas.needRedraw = true
end
function GuiElement:setSubFocus(widgetId, pageName)
if (pageName ~= nil) then
self.widget:switch(pageName)
end
self.widget:trySelectWidget(widgetId)
end
-- ACTION FUNCTIONS
-- Send actions to the widgets
function Menu:cancelAction()
self.widget:cancelAction()
if (self.cancelFunc ~= nil) then
self.cancelFunc(self)
else
self.widget:cancelAction()
end
end
function Menu:clear()
@ -137,8 +172,8 @@ end
-- UPDATE FUNCTIONS
function Menu:updateElement(dt)
self:update(dt)
self.widget:update(dt)
Menu.super.updateElement(self, dt)
end
-- DRAW FUNCTIONS
@ -146,9 +181,12 @@ end
function Menu:drawElement()
self:draw()
end
function Menu:drawFinalTexture()
if (self:haveFocus()) then
local x, y, w, h = self:getGraphicalCursorPosition()
self:drawGraphicalCursor(x, y, w, h)
self:drawGraphicalCursor(self.canvas.padding + x, self.canvas.padding + y, w, h)
end
end
@ -160,11 +198,21 @@ function Menu:drawCanvas()
end
function Menu:drawWidgetBackground(x, y, w, h)
end
function Menu:redraw()
self.widget:redraw()
Menu.super.redraw(self)
end
-- WIDGET FUNCTIONS
-- Handle widgets of the functions
function Menu:addWidget(newwidget)
self.widget:addWidget(newwidget)
self.canvas.needRedraw = true
end
function Menu:setCancelWidget(id)
@ -193,35 +241,23 @@ end
function Menu:setCursor(cursorid)
self.widget:setCursor(cursorid)
self.canvas.needRedraw = true
end
function Menu:moveCursor(new_selected)
self:playNavigationSound()
self.widget:moveCursorAbsolute(new_selected)
self.canvas.needRedraw = true
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:getWithType(name, "sfx"))
end
function Menu:setSound(soundasset)
self.sound.active = true
self.sound.asset = soundasset
end
function Menu:playNavigationSound()
if self.sound.active == true then
self.sound.asset:play()
end
self:playSFX("navigate")
end
function Menu:playSFX(name)
self.gui:playSFX(name)
end
-- VIEW FUNCTIONS

View File

@ -31,7 +31,6 @@ end
function View1D:reset()
self.firstSlot = 1
print("reset")
end
function View1D:updateFirstSlot(widgetID)

View File

@ -21,7 +21,7 @@
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 View1D = require "birb.modules.menusystem.menus.views.view1D"
local View1D = require "birb.modules.gui.menus.views.view1D"
local View2D = View1D:extend()
function View2D:new(colNumber, lineNumber)

View File

@ -26,8 +26,8 @@ local BaseWidget = Object:extend()
-- Initialize and configure the widget
function BaseWidget:new(menuName)
self:initWrapper()
self.menu = self:getMenuByName(menuName)
self.assets = self:getAssets()
self.destroyed = false
self.selectable = false
@ -37,23 +37,35 @@ function BaseWidget:new(menuName)
self.canvas = {}
self.canvas.texture = nil
self.canvas.needRedraw = true
self.canvas.isAnimated = false
self.order = 0
self:register()
self.func = nil
self.type = "select"
end
function BaseWidget:initWrapper()
self.scene = core.scenemanager.nextScene or core.scenemanager.currentScene
self.gui = self.scene.gui
self.assets = self.scene.assets
end
function BaseWidget:setFunc(func)
self.func = func
end
function BaseWidget:getMenuByName(name)
local gui = self:getGui()
return gui:getMenu(name)
assert(name ~= nil, "Name cant be nil")
return self.gui.elements[name]
end
function BaseWidget:getGui()
local scene = core.scenemanager.currentScene
return scene.menusystem
function BaseWidget:getScene()
return core.scenemanager.nextScene or core.scenemanager.currentScene
end
function BaseWidget:getAssets()
local scene = core.scenemanager.currentScene
local scene = core.scenemanager.nextScene or core.scenemanager.currentScene
return scene.assets
end
@ -62,7 +74,13 @@ function BaseWidget:register()
self.menu:addWidget(self)
end
function BaseWidget:redrawCanvas()
function BaseWidget:redraw()
if (self.canvas.needRedraw or self.canvas.isAnimated) then
self:generateTexture()
end
end
function BaseWidget:generateTexture()
self.width, self.height = self.menu:getWidgetSize(self.id)
local canvas = love.graphics.newCanvas(self.width, self.height)
@ -123,9 +141,6 @@ end
-- Update the widget
function BaseWidget:update(dt)
if (self.canvas.needRedraw) then
self:redrawCanvas()
end
-- N/A
end
@ -139,8 +154,14 @@ end
-- ACTION FUNCTION
-- Functions to handle actions and selection.
function BaseWidget:playSFX()
self.menu:playSFX(self.type)
end
function BaseWidget:action(source)
--self:destroy()
if (self.func ~= nil) then
self.func(self)
end
end
function BaseWidget:destroy()

View File

@ -24,7 +24,7 @@
local Widget = {}
-- Add the widget as subvariable to the returned table
Widget.Base = require "birb.modules.menusystem.menus.widgets.base"
Widget.Text = require "birb.modules.menusystem.menus.widgets.text"
Widget.Base = require "birb.modules.gui.menus.widgets.base"
Widget.Text = require "birb.modules.gui.menus.widgets.text"
return Widget

View File

@ -20,7 +20,7 @@
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 BaseWidget = require "birb.modules.menusystem.menus.widgets.base"
local BaseWidget = require "birb.modules.gui.menus.widgets.base"
local TextWidget = BaseWidget:extend()
-- TEXT WIDGET
@ -35,13 +35,14 @@ function TextWidget:new(menuName, font, label, position, padding)
-- We add the first label
local position = position or "center"
self:addLabel(label, position)
self:setSelectedColor(1, 1, 1)
self:setColor(1, 1, 1)
end
function TextWidget:addLabel(label, position)
local complexLabel = {}
assert(label ~= nil, "Label can't be nil")
complexLabel.label = label
complexLabel.position = position
complexLabel.position = position or "left"
table.insert(self.labels, complexLabel)
end
@ -49,6 +50,13 @@ function TextWidget:replaceLabel(id, newLabel)
self.labels[id].label = newLabel
end
function TextWidget:setColor(r, g, b)
self.color = {}
self.color.r = r
self.color.g = g
self.color.b = b
end
function TextWidget:setSelectedColor(r, g, b)
self.selectedColor = {}
self.selectedColor.r = r
@ -61,13 +69,29 @@ function TextWidget:getFont()
end
function TextWidget:getSelectedColor()
return self.selectedColor.r, self.selectedColor.g, self.selectedColor.b
if (self.selectedColor ~= nil) then
return self.selectedColor.r, self.selectedColor.g, self.selectedColor.b
else
return self:getColor()
end
end
function TextWidget:getColor()
return self.color.r, self.color.g, self.color.b
end
function TextWidget:getPadding()
return self.padding
end
function TextWidget:getPaddingLeft()
return self.paddingLeft or self:getPadding()
end
function TextWidget:getPaddingRight()
return self.paddingRight or self:getPadding()
end
function TextWidget:drawCanvas()
local w, h
local font = self:getFont()
@ -77,18 +101,31 @@ function TextWidget:drawCanvas()
if (complexLabel.position == "center") then
w = math.floor(self.width / 2)
elseif (complexLabel.position == "left") then
w = self:getPadding()
w = self:getPaddingLeft()
elseif (complexLabel.position == "right") then
w = math.floor(self.width - self:getPadding())
w = math.floor(self.width - self:getPaddingRight())
else
error("Position " .. complexLabel.position .. " is unknown for label " .. complexLabel.label)
end
font:draw(complexLabel.label, w, h, -1, complexLabel.position)
end
end
function TextWidget:draw(x, y, w, h)
local r, g, b = self:getColor()
love.graphics.setColor(r, g, b, 1)
if self.canvas.texture ~= nil then
love.graphics.draw(self.canvas.texture, x, y)
end
utils.graphics.resetColor()
end
function TextWidget:drawSelected(x, y, w, h)
local r, g, b = self:getSelectedColor()
love.graphics.setColor(r, g, b, 1)
self:draw(x, y)
if self.canvas.texture ~= nil then
love.graphics.draw(self.canvas.texture, x, y)
end
utils.graphics.resetColor()
end

View File

@ -0,0 +1,94 @@
local ElementList = Object:extend()
function ElementList:initElements()
self.elements = {}
self.focusedElement = nil
self.lastFocused = nil
self.nbrElement = 0
end
function ElementList:addElement(name, element)
self.nbrElement = self.nbrElement + 1
self.elements[name] = element
return self.nbrElement
end
function ElementList:deleteElement(name)
self.elements[name] = nil
end
function ElementList:setFocus(name, widgetId, page)
assert(self:elementExists(name), "Element " .. name .. " doesn't exists")
self:storeLastFocus()
self.focusedElement = name
self.elements[name].isVisible = true
if (widgetId ~= nil) then
self.elements[name]:setSubFocus(widgetId, page)
end
end
function ElementList:removeFocus()
self:storeLastFocus()
self.focusedElement = nil
end
function ElementList:storeLastFocus()
if (self.focusedElement ~= nil) then
self.lastFocused = self.focusedElement
end
end
function ElementList:setLastFocus()
if (self:elementExists(self.lastFocused)) then
self:setFocus(self.lastFocused)
end
end
function ElementList:elementExists(name)
return (self:getElement(name) ~= nil)
end
function ElementList:haveFocus()
return self:elementIsVisible(self.focusedElement)
end
function ElementList:getFocusedElement()
return self:getElement(self.focusedElement)
end
function ElementList:getElement(name)
if (not utils.string.isEmpty(name)) then
return self.elements[name]
end
return nil
end
function ElementList:getVisibleElement(topLayer)
local visibleList = {}
for _, element in pairs(self.elements) do
if (element ~= nil) then
if (element:getVisibility() and ((element.depth) < 0 == topLayer)) then
table.insert(visibleList, element)
end
end
end
table.sort(visibleList, function (a, b)
if (a.depth == b.depth) then
return (a.creationId < b.creationId)
else
return (a.depth > b.depth)
end
end)
return visibleList
end
function ElementList:elementIsVisible(name)
if (self:elementExists(name)) then
return self.elements[name]:getVisibility()
end
return false
end
return ElementList

View File

@ -0,0 +1,20 @@
local ScreenList = Object:extend()
function ScreenList:initScreens()
self.screens = {}
end
function ScreenList:addScreen(name, screen)
self.screens[name] = screen
end
function ScreenList:deleteScreen(name)
self.screens[name]:purgeElements()
self.screens[name] = nil
end
function ScreenList:getScreen(name)
return self.screens[name]
end
return ScreenList

View File

@ -0,0 +1,160 @@
local GuiScreen = Object:extend()
local ElementList = require "birb.modules.gui.mixins.elements"
GuiScreen:implement(ElementList)
local TweenManager = require "birb.classes.time"
local ScreenSet = require "birb.modules.gui.screen.screenset"
local elementDataStruct = require "birb.structures.elementData"
function GuiScreen:new(name)
self:initWrapper()
self.name = name
self.isVisible = false
self.transforms = {}
self.tweens = TweenManager(self)
self:reset()
self:registerElements()
self.gui:addScreen(name, self)
self.defaultFocus = nil
end
function GuiScreen:initWrapper()
local scene = core.scenemanager.nextScene or core.scenemanager.currentScene
self.scene = scene
self.gui = scene.gui
-- Présent pour la compatibilité
self.controller = self.gui
self.assets = scene.assets
end
function GuiScreen:update(dt)
self.tweens:update(dt)
end
function GuiScreen:show(focusElement, widgetId, page)
self:showSimple(focusElement, widgetId, page)
if (self.set ~= nil) then
self.set.owner:show()
end
end
function GuiScreen:showSimple(focusElement, widgetId, page)
focusElement = focusElement or self.defaultFocus
if (not self.isVisible) then
self.isVisible = true
local delay = 0
if (self.set ~= nil) then
delay = self.set:setCurrentScreen(self.name)
end
if (self.transforms["show"] ~= nil) then
if (delay == 0) then
self:showWithSubScreen(focusElement, widgetId, page)
else
self.tweens:newFunc(delay, "focus", function () self:showWithSubScreen(focusElement, widgetId, page) end)
end
end
end
end
function GuiScreen:showWithSubScreen(focusElement, widgetId, page)
self:playTransform("show")
if (focusElement ~= nil) then
self.gui:setFocus(focusElement, widgetId, page)
end
if (self.subscreens ~= nil) then
self.subscreens:show()
end
end
function GuiScreen:setDatas(datas)
end
function GuiScreen:hide()
local time = 0
if (self.isVisible) then
if (self.transforms["hide"] ~= nil) then
time = self:playTransform("hide")
self.tweens:newFunc(time, "hide", function ()
self.isVisible = false
end)
end
if (self.subscreens ~= nil) then
self.subscreens:hideCurrent()
end
end
return time
end
function GuiScreen:addTransform(name, transform)
self.transforms[name] = transform
end
function GuiScreen:playTransform(name, delay)
return self.gui:transform(self.transforms[name], delay)
end
function GuiScreen:reset()
self:initElements()
end
function GuiScreen:registerElements()
local elementList = self:createElements()
for _, rawElement in ipairs(elementList) do
if (rawElement.is ~= nil) then
self:addElement(rawElement.name, rawElement)
rawElement.screen = self
else
local elemData = utils.table.parse(rawElement, elementDataStruct, 3)
local element = elemData.element
self:addElement(element.name, element)
if (elemData.focus == true) then
element:getFocus()
end
if (elemData.delay > 0) then
element.isVisible = false
element:newSwitch(elemData.delay, {"isVisible"})
end
if (elemData.depth ~= nil) then
element.depth = elemData.depth
end
if (elemData.keypress ~= nil) then
element:setKeyPressAction(elemData.keypress)
end
element.screen = self
end
end
end
function GuiScreen:createElements()
-- Empty function
end
function GuiScreen:setParentSet(set)
self.set = set
end
function GuiScreen:addSubscreen(screen)
self:initSubscreen()
self.subscreens:add(screen)
end
function GuiScreen:showSubscreen(screenname)
if (self.subscreens ~= nil) then
self.subscreens:show(screenname)
end
end
function GuiScreen:initSubscreen()
if (self.subscreens == nil) then
self.subscreens = ScreenSet(self)
end
end
return GuiScreen

View File

@ -0,0 +1,73 @@
-- screens/screenset :: a set of exclusive screens
-- Useful to handle a complexe menu with several screens
--[[
Copyright © 2021 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 ScreenSet = Object:extend()
function ScreenSet:new(owner)
self.set = {}
self.defaultScreen = ""
self.currentScreen = ""
self.owner = owner
self.delay = 0
end
function ScreenSet:show(screenName)
local screenName = screenName
if (screenName == nil) then
if self.currentScreen == "" then
screenName = self.defaultScreen
else
return
end
end
if screenName ~= "" then
self.set[screenName]:showSimple()
end
end
function ScreenSet:setCurrentScreen(screenName)
screenName = screenName or self.defaultScreen
local time = self:hideCurrent() + self.delay
self.currentScreen = screenName
return time
end
function ScreenSet:hideCurrent()
local time = 0
if (self.currentScreen ~= "") then
time = self.set[self.currentScreen]:hide()
self.currentScreen = ""
end
return time
end
function ScreenSet:add(screen, setAsDefault)
self.set[screen.name] = screen
if (setAsDefault == true or self.defaultScreen == "") then
self.defaultScreen = screen.name
end
screen:setParentSet(self)
end
return ScreenSet

View File

@ -24,24 +24,49 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local ListBox = require "birb.modules.menusystem.menus.listbox"
local ListBox = require "birb.modules.gui.menus.listbox"
local TextMenu = ListBox:extend()
TextMenu.baseWidgets = require "birb.modules.menusystem.textmenu.widgets"
TextMenu.baseWidgets = require "birb.modules.gui.textmenu.widgets"
local BASE_PADDING = 8
function TextMenu:new(name, font, x, y, w, slotNumber, padding)
function TextMenu:new(name, font, x, y, w, slotNumber, padding, lineSize)
lineSize = lineSize or 1
self:setFont(font)
self.lineHeight = self.font:getHeight() * lineSize
self.name = name
local h = self.font:getHeight() * slotNumber
self.padding = padding or BASE_PADDING
self:setSelectedColor(1,1,1)
TextMenu.super.new(self, nil, name, x, y, w, h, slotNumber)
TextMenu.super.new(self, name, x, y, w, (self.lineHeight * slotNumber), slotNumber)
end
function TextMenu:addItem(text, position, func, type, additionnalItems, color, additionnalDatas)
local widget = TextMenu.baseWidgets.Base(self.name, text, position)
widget:setFunc(func)
widget.type = type or "select"
if (additionnalItems ~= nil) then
for _, item in ipairs(additionnalItems) do
widget:addLabel(item[1], item[2])
end
end
if (color ~= nil) then
widget:setColor(color[1], color[2], color[3])
end
if (additionnalDatas ~= nil) then
widget.datas = additionnalDatas
end
end
function TextMenu:generateSubmenu(pageName, label, parent, list, func, backWidget)
self:addSubmenu(pageName, label, parent, backWidget)
for _, data in ipairs(list) do
self:addItem(func(data))
end
end
function TextMenu:setFont(fontName)
local scene = core.scenemanager.currentScene
local scene = core.scenemanager.nextScene or core.scenemanager.currentScene
self.font = scene.assets:getFont(fontName)
end
@ -57,13 +82,22 @@ function TextMenu:setSelectedColor(r, g, b)
end
function TextMenu:getSelectedColor()
return self.selectedColor.r, self.selectedColor.g, self.selectedColor.b
return self.selectedColor
end
function TextMenu:getPadding()
return self.padding
end
function TextMenu:getPaddingLeft()
return self.paddingLeft or self:getPadding()
end
function TextMenu:getPaddingRight()
return self.paddingRight or self:getPadding()
end
function TextMenu:addSubmenu(pageName, label, parent, backWidget)
local label = label or pageName
local parent = parent or "main"
@ -72,6 +106,7 @@ function TextMenu:addSubmenu(pageName, label, parent, backWidget)
TextMenu.super.addSubmenu(self, pageName, parent)
if (backWidget ~= false) then
TextMenu.baseWidgets.Back(self.name)
self:setCancelWidget("last")
end
end

View File

@ -21,7 +21,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local TextMenuWidget = require "birb.modules.menusystem.textmenu.widgets.basic"
local TextMenuWidget = require "birb.modules.gui.textmenu.widgets.basic"
local BackWidget = TextMenuWidget:extend()

View File

@ -23,7 +23,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local TextWidget = require "birb.modules.menusystem.menus.widgets.text"
local TextWidget = require "birb.modules.gui.menus.widgets.text"
local TextMenuWidget = TextWidget:extend()
@ -37,11 +37,24 @@ function TextMenuWidget:getFont()
end
function TextMenuWidget:getSelectedColor()
return self.menu:getSelectedColor()
local selectedColor = self.menu:getSelectedColor()
if (selectedColor ~= nil) then
return selectedColor.r, selectedColor.g, selectedColor.b
else
return self:getColor()
end
end
function TextMenuWidget:getPadding()
return self.menu:getPadding()
end
function TextWidget:getPaddingLeft()
return self.menu:getPaddingLeft() or self:getPadding()
end
function TextWidget:getPaddingRight()
return self.menu:getPaddingRight() or self:getPadding()
end
return TextMenuWidget

View File

@ -24,8 +24,8 @@
local Widget = {}
-- Add the widget as subvariable to the returned table
Widget.Base = require "birb.modules.menusystem.textmenu.widgets.basic"
Widget.SubMenu= require "birb.modules.menusystem.textmenu.widgets.submenu"
Widget.Back = require "birb.modules.menusystem.textmenu.widgets.back"
Widget.Base = require "birb.modules.gui.textmenu.widgets.basic"
Widget.SubMenu= require "birb.modules.gui.textmenu.widgets.submenu"
Widget.Back = require "birb.modules.gui.textmenu.widgets.back"
return Widget

View File

@ -22,7 +22,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local TextMenuWidget = require "birb.modules.menusystem.textmenu.widgets.basic"
local TextMenuWidget = require "birb.modules.gui.textmenu.widgets.basic"
local SubmenuWidget = TextMenuWidget:extend()

View File

@ -1,33 +0,0 @@
-- modules : different modules that are usable as part of birb
--[[
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 modules = {}
local cwd = (...):gsub('%.init$', '') .. "."
modules.Assets = require(cwd .. "assets")
modules.GameSystem = require(cwd .. "gamesystem")
modules.MenuSystem = require(cwd .. "menusystem")
modules.Timers = require(cwd .. "timers")
return modules

View File

@ -1,285 +0,0 @@
-- 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 .. "menus.parent")
MenuSystem.ListBox = require(cwd .. "menus.listbox")
MenuSystem.FlowBox = require(cwd .. "menus.flowbox")
MenuSystem.Grid = require(cwd .. "menus.grid")
-- load widgets object
MenuSystem.Widget = require(cwd .. "menus.widgets")
-- INIT FUNCTIONS
-- Initialize and configure the menu controller
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:getMenu(name)
return self.menus[name]
end
function MenuSystem:menuExist(name)
return (self.menus[name] ~= nil)
end
function MenuSystem:switchMenu(menuName)
for name, guiElement in pairs(self.menus) do
if (name == menuName) then
guiElement:getFocus()
guiElement:setVisibility(true)
guiElement:setActivity(true)
else
guiElement:setVisibility(false)
guiElement: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 _, guiElement in pairs(self.menus) do
guiElement:setVisibility(visibility)
end
end
function MenuSystem:setAllMenuActivity(activity)
for _, guiElement in pairs(self.menus) do
guiElement.isActive = activity
end
end
function MenuSystem:removeDestroyedMenus()
-- On retire les entitées marquées comme supprimées
for name, guiElement in pairs(self.menus) do
if (guiElement.isDestroyed == true) then
self.menus[name] = nil
end
end
end
-- SOUND FUNCTIONS
-- Add sounds to every menus
-- TODO: rework to be used directly by widgets later
function MenuSystem:setSoundFromSceneAssets(soundname)
for _, guiElement in pairs(self.menus) do
guiElement:setSoundFromSceneAssets(soundname)
end
end
function MenuSystem:setSound(soundasset)
for _, guiElement in pairs(self.menus) do
guiElement:setSound(soundasset)
end
end
-- UPDATE FUNCTIONS
-- Update the menus of the menusystem
function MenuSystem:update(dt)
if (self.isActive) then
self:removeDestroyedMenus()
for _, guiElement in pairs(self.menus) do
guiElement:updateElement(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 keyname, keydata in pairs(self.keys) do
if self.keys[keyname].isPressed then
self.menus[self.focusedMenu]:keyreleased(keyname)
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 _, guiElement in pairs(self.menus) do
if guiElement.isActive then
if guiElement:areCoordInside(x, y) then
local xx, yy = guiElement:getRelativeCoordinate(x, y)
guiElement:mousemoved(xx, yy)
break;
end
end
end
end
end
function MenuSystem:mousepressed( x, y, button, istouch )
if (self.isActive) then
for _, guiElement in pairs(self.menus) do
if guiElement.isActive then
if guiElement:areCoordInside(x, y) then
local xx, yy = guiElement:getRelativeCoordinate(x, y)
guiElement:mousepressed(xx, yy, button, istouch )
break;
end
end
end
end
end
-- DRAW FUNCTIONS
-- All functions to draw the menus of the menusystem
function MenuSystem:getDrawList()
local drawList = {}
for name, guiElement in pairs(self.menus) do
local drawObject = {}
drawObject.name = name
drawObject.depth = guiElement.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 _, drawObject in ipairs(self.drawList) do
local guiElement = self.menus[drawObject.name]
if (guiElement.isVisible) then
guiElement:drawElement(dt)
end
end
end
end
return MenuSystem

View File

@ -1,119 +0,0 @@
local Rect = require "birb.objects.2D.rect"
local GuiElement = Rect:extend()
function GuiElement:new(name, x, y, w, h)
GuiElement.super.new(self, x, y, w, h)
self.name = name
self.isDestroyed = false
self.isVisible = true
self.isActive = true
self.isLocked = false
self.isAlwaysVisible = false
self.depth = 0
self:register()
end
function GuiElement:getGui()
local scene = core.scenemanager.currentScene
return scene.menusystem
end
function GuiElement:register()
local gui = self:getGui()
gui:addMenu(self.name, self)
end
function GuiElement:destroy()
self.destroyed = true
end
-- VISIBILITY/ACTIVITY
-- Handle drawing and how we interact with
function GuiElement:setDepth(depth)
self.depth = depth or 0
end
function GuiElement:setVisibility(visibility)
if self.isLocked == false and self.isAlwaysVisible == false then
self.isVisible = visibility
else
-- if the element is locked (thus is always active), it should also
-- be always visible.
self.isVisible = true
end
end
function GuiElement:setActivity(activity)
if self.isLocked == false then
self.isActive = activity
else
self.isActive = true
end
end
function GuiElement:lock(lock)
self.isLocked = lock
end
function GuiElement:lockVisibility(lock)
self.isAlwaysVisible = lock
end
function GuiElement:getFocus()
local gui = self:getGui()
gui.focusedMenu = self.name
end
function GuiElement:haveFocus()
local gui = self:getGui()
return (gui.focusedMenu == self.name)
end
-- UPDATE FUNCTIONS
-- Update the menu every game update
-- External update function
function GuiElement:updateElement(dt)
self:update(dt)
end
-- Internal update function
function GuiElement:update(dt)
-- Cette fonction ne contient rien par défaut
end
-- DRAW FUNCTIONS
-- Draw the menu and its content
function GuiElement:drawElement()
self:draw()
end
function GuiElement:draw()
-- nothing here
end
-- KEYBOARD FUNCTIONS
-- Handle key press
function GuiElement:keyreleased(key)
-- Cette fonction ne contient rien par défaut
end
-- MOUSE FUNCTIONS
-- Handle pointers (clic/touch)
function GuiElement:mousemoved(x, y)
-- Cette fonction ne contient rien par défaut
end
function GuiElement:mousepressed(x, y, button, istouch)
-- Cette fonction ne contient rien par défaut
end
return GuiElement

View File

@ -27,35 +27,29 @@ local cwd = (...):gsub('%.scenes$', '') .. "."
local Scene = Object:extend()
local Assets = require(cwd .. "assets")
local MenuSystem = require(cwd .. "menusystem")
local Gui = require (cwd .. "gui")
-- INIT FUNCTIONS
-- Initialize and configure the scene
function Scene:new(args)
function Scene:new()
self.mouse = {}
self.mouse.x, self.mouse.y = core.screen:getMousePosition()
self.assets = Assets()
self.menusystem = MenuSystem(self)
self.sources = core.input:getSources()
self.gui = Gui(self)
self.inputLocked = true
self.inputLockedTimer = 2
self:flushKeys()
self.isActive = false
self.args = args
self.dialog = nil
self:initWorld()
self:register()
end
function Scene:start()
-- Empty function
end
function Scene:register()
core.scenemanager:setScene(self)
end
@ -64,13 +58,18 @@ function Scene:clear()
-- TODO: send automatic cleanups to the different elements of the scene
end
function Scene:restored()
end
-- UPDATE FUNCTIONS
-- Handle stuff that happens every steps
function Scene:updateScene(dt)
self:updateStart(dt)
self:setKeys()
self.menusystem:update(dt)
self.assets:update(dt)
self:updateGUI(dt)
self:updateWorld(dt)
self:update(dt)
self:updateEnd(dt)
@ -89,11 +88,20 @@ function Scene:updateEnd(dt)
end
function Scene:updateWorld(dt)
if (self.world ~= nil) and (self.world.isActive) then
if ((self.world ~= nil) and (self.world.isActive) and (self.dialog == nil)) then
self.world:update(dt)
end
end
function Scene:updateGUI(dt)
if (self.gui ~= nil) then
self.gui:update(dt)
if (core.screen:isActive()) then
self.gui:keycheck(self:getKeys(1))
end
end
end
-- MOUSE FUNCTIONS
-- Make the scene support the pointer
@ -130,12 +138,19 @@ end
-- DRAW FUNCTIONS
-- Draw the scene and its content
function Scene:redraw()
self.gui:redraw()
end
function Scene:drawScene()
self:drawStart()
self:drawWorld()
self:draw()
self.menusystem:draw()
self.gui:drawBottom()
self:drawEnd()
core.screen:drawTransition()
self.gui:drawTop()
self:drawOverTransition()
end
function Scene:drawStart()
@ -150,6 +165,10 @@ function Scene:drawEnd()
end
function Scene:drawOverTransition()
end
function Scene:drawWorld(dt)
if (self.world ~= nil) then
self.world:draw()
@ -160,36 +179,35 @@ end
-- Handle inputs from keyboard/controllers
function Scene:setKeys()
if (self.inputLocked) or (not self.isActive) then
if (self.inputLocked) then
self.inputLockedTimer = self.inputLockedTimer - 1
if (self.inputLockedTimer <= 0 ) then
self.inputLocked = false
end
self.menusystem.keys = self:getKeys(1)
else
self.sources = core.input.sources
self.menusystem.keys = self:getKeys(1)
end
end
function Scene:getKeys(sourceid)
if sourceid == nil then
core.debug:logWarn("scene", "no sourceid detected, will default to 1")
core.debug:warning("scene", "no sourceid detected, will default to 1")
end
local sourceid = sourceid or 1
if (self.inputLocked) or (not self.isActive) then
core.debug:logDebug("scene", "inputs are currently locked")
if (self.inputLocked) then
core.debug:print("scene", "inputs are currently locked")
return core.input.fakekeys
else
return self.sources[sourceid].keys
end
end
function Scene:flushKeys(timer)
function Scene:flushKeys()
core.input:flushKeys()
self.sources = core.input.sources
self.inputLockedTimer = timer or 2
self.sources = core.input:getSources()
self.inputLockedTimer = 2
self.inputLocked = true
end

View File

@ -0,0 +1,33 @@
local TransitionParent = require "birb.modules.transitions.parent"
local CanvasTransition = TransitionParent:extend()
function CanvasTransition:new(func, ox, oy, fadeOut, easeIn, easeOut, duration, wait)
CanvasTransition.super.new(self, func, ox, oy, fadeOut, easeIn, easeOut, duration, wait)
self:generateCanvas(0)
end
function CanvasTransition:update(dt)
CanvasTransition.super.update(self, dt)
self:generateCanvas(dt)
end
function CanvasTransition:generateCanvas(dt)
self.canvas = love.graphics.newCanvas(424, 240)
love.graphics.setCanvas(self.canvas)
self:drawCanvas(dt)
love.graphics.setCanvas()
end
function CanvasTransition:drawCanvas(dt)
end
function CanvasTransition:draw()
if (self.canvas ~= nil) then
love.graphics.setBlendMode("multiply", "premultiplied")
love.graphics.draw(self.canvas, 0, 0)
love.graphics.setBlendMode("alpha")
end
end
return CanvasTransition

View File

@ -0,0 +1,16 @@
local TransitionParent = require "birb.modules.transitions.canvas"
local DefaultTransition = TransitionParent:extend()
function DefaultTransition:new(func, ox, oy, fadeOut)
DefaultTransition.super.new(self, func, ox, oy, fadeOut, "inQuad", "outQuad", 0.8, 0.1)
end
function DefaultTransition:drawCanvas()
love.graphics.setColor(0,0,0,1)
love.graphics.rectangle("fill", 0, 0, 424, 240)
utils.graphics.resetColor()
local value = 1 - self.value
love.graphics.circle("fill",self.ox, self.oy, (424/2) * 1.5 * value)
end
return DefaultTransition

View File

@ -0,0 +1,22 @@
local TransitionParent = require "birb.modules.transitions.canvas"
local DecalTransition = TransitionParent:extend()
function DecalTransition:new(func, ox, oy, fadeOut, decal)
self.decal = decal
DecalTransition.super.new(self, func, ox, oy, fadeOut, "inQuad", "outQuad", 0.8, 0.1)
end
function DecalTransition:loadResources()
self.decalDrawable = love.graphics.newImage("assets/transitions/" .. self.decal .. ".png")
end
function DecalTransition:drawCanvas()
love.graphics.setColor(0,0,0,1)
love.graphics.rectangle("fill", 0, 0, 424, 240)
utils.graphics.resetColor()
local w, h = self.decalDrawable:getDimensions()
local value = (1 - self.value) * (240 / h) * 4
love.graphics.draw(self.decalDrawable,self.ox,self.oy,0,value, value, w / 2, h / 2)
end
return DecalTransition

View File

@ -0,0 +1,14 @@
local TransitionParent = require "birb.modules.transitions.parent"
local DefaultTransition = TransitionParent:extend()
function DefaultTransition:new(func, ox, oy, fadeOut)
DefaultTransition.super.new(self, func, ox, oy, fadeOut, "inQuad", "outQuad", 0.35, 0.1)
end
function DefaultTransition:draw()
love.graphics.setColor(0,0,0,self.value)
love.graphics.rectangle("fill", 0, 0, 424, 240)
utils.graphics.resetColor()
end
return DefaultTransition

View File

@ -0,0 +1,4 @@
return {
default = require "birb.modules.transitions.default",
circle = require "birb.modules.transitions.circle"
}

View File

@ -0,0 +1,42 @@
local TransitionParent = Object:extend()
local TweenManager = require "birb.classes.time"
function TransitionParent:new(func, ox, oy, fadeOut, easeIn, easeOut, duration, wait)
self.tween = TweenManager(self)
self:loadResources()
self.func = func
self.ox = ox or 0
self.oy = oy or 0
self.fadeOut = fadeOut
if (self.fadeOut) then
self.value = 1
self.tween:newTween(wait, duration, {value = 0}, easeOut)
else
self.value = 0
self.tween:newTween(0, duration, {value = 1}, easeIn)
end
self.tween:newTimer(duration + wait, "isOver")
end
function TransitionParent:loadResources()
--vide par defaut
end
function TransitionParent:update(dt)
self.tween:update(dt * 1.5)
end
function TransitionParent:timerResponse(timer)
if (timer == "isOver") then
if (self.func ~= nil) then
self.func()
end
core.screen:transitionOver(self.fadeOut)
end
end
function TransitionParent:draw()
end
return TransitionParent

View File

@ -21,10 +21,9 @@
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 Rect = require "birb.classes.2D.rect"
local Rect = require "birb.objects.2D.rect"
local BaseActor = require("birb.modules.world.actors.mixins.base")
local BaseActor = require "birb.modules.world.actors.mixins.base"
local SpritedActor = require("birb.modules.world.actors.mixins.sprites")
local TimedActor = require("birb.modules.world.actors.mixins.timers")
local InputActor = require("birb.modules.world.actors.mixins.inputs")
@ -37,13 +36,14 @@ Actor2D:implement(TimedActor)
Actor2D:implement(InputActor)
Actor2D:implement(PhysicalActor)
local Hitbox = require("birb.modules.world.actors.utils.hitbox2D")
local Hitbox = require "birb.modules.world.actors.utils.hitbox2D"
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
function Actor2D:new(world, type, x, y, w, h, isSolid)
Actor2D.super.new(self, x, y, w, h)
self:init(world, type)
self:initPhysics(Hitbox, isSolid)
self:initTimers()
@ -51,10 +51,6 @@ function Actor2D:new(world, type, x, y, w, h, isSolid)
self:initKeys()
end
function Actor2D:packForHitbox()
return {0, 0, self.w, self.h}
end
function Actor2D:destroy()
self.world:removeActor(self)
self.mainHitbox:destroy()
@ -134,6 +130,13 @@ function Actor2D:getViewCenter()
return x, y
end
-- HITBOXES FUNCTIONS
-- Functions related to actor hitboxes
function Actor2D:packForHitbox()
return {0, 0, self.w, self.h}
end
-- DRAW FUNCTIONS
-- Draw the actors.

View File

@ -22,7 +22,9 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local BasicBox = require "birb.objects.3D.box"
local Hitbox = require("birb.modules.world.actors.utils.hitbox3D")
local Boxes = require("birb.modules.world.actors.utils.boxes")
local BasicBox = require "birb.classes.3D.box"
local BaseActor = require("birb.modules.world.actors.mixins.base")
local SpritedActor = require("birb.modules.world.actors.mixins.sprites")
@ -39,9 +41,6 @@ Actor3D:implement(InputActor)
Actor3D:implement(PhysicalActor)
Actor3D:implement(Shape3DActor)
local Hitbox = require("birb.modules.world.actors.utils.hitbox3D")
local Boxes = require("birb.modules.world.actors.utils.boxes")
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
@ -51,6 +50,7 @@ function Actor3D:new(world, type, x, y, z, w, h, d, isSolid)
self:initPhysics(Hitbox, isSolid)
self:initTimers()
self:initSprite()
self:initKeys()
self:initShape(Boxes, true)
end
@ -61,11 +61,6 @@ function Actor3D:destroy()
self.isDestroyed = true
end
function Actor3D:packForHitbox()
return {0, 0, 0, self.w, self.h, self.d}
end
-- PHYSICS FUNCTIONS
-- Handle movement and collisions.
@ -152,6 +147,13 @@ function Actor3D:getViewCenter()
return x, y - (self.d/2)
end
-- HITBOXES FUNCTIONS
-- Functions related to actor hitboxes
function Actor3D:packForHitbox()
return {0, 0, 0, self.w, self.h, self.d}
end
-- DRAW FUNCTIONS
-- Draw the actors.
@ -161,10 +163,6 @@ function Actor3D:drawShadow(x, y)
utils.graphics.resetColor()
end
function Actor3D:getShape()
return (self.x), (self.y - self.z - self.d), self.w, (self.h + self.d)
end
function Actor3D:draw()
self:drawStart()
if (self.box ~= nil) then

View File

@ -26,15 +26,14 @@ local Actor2D = require(cwd .. "actor2D")
local GFX = Actor2D:extend()
function GFX:new(world, x, y, spritename)
local baseSprite = world.scene.assets:getWithType(spritename, "sprite")
local width, height = baseSprite:getDimensions()
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, true)
end
function GFX:animationEnded(animation)
core.debug:logDebug("gfx2D", 'Current animation "' .. animation .. '" have ended, destroying gfx')
core.debug:print("gfx2D", 'Current animation "' .. animation .. '" have ended, destroying gfx')
self:destroy()
end

View File

@ -26,15 +26,14 @@ local Actor3D = require(cwd .. "actor3D")
local GFX = Actor3D:extend()
function GFX:new(world, x, y, z, spritename)
local baseSprite = world.scene.assets:getWithType(spritename, "sprite")
local width, height = baseSprite:getDimensions()
local width, height = world.scene.assets.sprites[spritename]:getDimensions()
GFX.super.new(self, world, "gfx", x - (width/2), y - (width/2), z - (height/2), width, width, height)
self:setSprite(spritename, true)
end
function GFX:animationEnded(animation)
core.debug:logDebug("gfx3D", 'Current animation "' .. animation .. '" have ended, destroying gfx')
core.debug:print("gfx3D", 'Current animation "' .. animation .. '" have ended, destroying gfx')
self:destroy()
end

View File

@ -31,13 +31,27 @@ function BaseActor:init(world, type)
self.type = type or ""
self.depth = 0
self.updateFunctions = {}
self:setManagers(world)
self:initKeys()
self:setDebugColor(1, 1, 1)
self:register()
self.useTileCollision = true
end
self.updateFunctions = {}
function BaseActor:setProperties(properties)
-- Do something here
end
function BaseActor:addUpdateFunction(func)
table.insert(self.updateFunctions, func)
end
function BaseActor:applyUpdateFunctions(dt)
for key, func in ipairs(self.updateFunctions) do
func(self, dt)
end
end
function BaseActor:setManagers(world)
@ -77,16 +91,6 @@ function BaseActor:update(dt)
self:updateEnd(dt)
end
function BaseActor:addUpdateFunction(func)
table.insert(self.updateFunctions, func)
end
function BaseActor:applyUpdateFunctions(dt)
for key, func in ipairs(self.updateFunctions) do
func(self, dt)
end
end
function BaseActor:updateEnd(dt)
end
@ -107,8 +111,4 @@ function BaseActor:drawEnd()
end
function BaseActor:drawHUD(id, height, width)
end
return BaseActor

View File

@ -1,4 +1,4 @@
-- SpritedActor.lua :: Get input from the world object.
-- InputActor.lua :: Get input from the world object.
--[[
Copyright © 2019 Kazhnuz

View File

@ -1,10 +1,11 @@
PhysicalActor = Object:extend()
local hitboxStructure = require "birb.structures.hitbox"
-- PHYSICS FUNCTIONS
-- Raw implementation of everything common in physics
function PhysicalActor:initPhysics(hitboxObj, isSolid)
self.isSolid = isSolid or false
self.isSolid = isSolid or false
self.xsp = 0
self.ysp = 0
@ -143,7 +144,8 @@ function PhysicalActor:initHitboxes(hitboxObj)
end
function PhysicalActor:initMainHitbox()
-- Empty function : don't load ANY real hitbox function into PhysicalActor
self.mainHitbox = self.Hitbox(self, self.type, self:packForHitbox(), 0, 0, self.isSolid)
self.mainHitbox:advertiseAsMainHitbox()
end
function PhysicalActor:setHitboxFile(file)
@ -175,28 +177,22 @@ end
function PhysicalActor:addHitboxFromFrameData(framedata, animationID, frameID, hitboxID)
local sx, sy = self.sprite:getScalling()
local type = framedata[1]
local box = framedata[2]
local isSolid = framedata[3] or false
local hitbox = utils.table.parse(framedata, hitboxStructure, 1)
local anim = animationID or "null"
local frame = frameID or 0
local id = hitboxID or 0
if (type == "main") then
self.mainHitbox:setFromData(box, sx, sy)
if (hitbox.type == "main") then
self.mainHitbox:setFromData(hitbox.box, sx, sy)
else
local hitboxName = anim .. frame .. type .. id
self:addHitbox(hitboxName, type, box, sx, sy, isSolid)
local hitboxName = anim .. frame .. hitbox.type .. id
self:addHitbox(hitboxName, hitbox.type, hitbox.box, sx, sy, hitbox.isSolid)
return hitboxName
end
end
function PhysicalActor:initMainHitbox()
self.mainHitbox = self.Hitbox(self, self.type, self:packForHitbox(), 0, 0, self.isSolid)
self.mainHitbox:advertiseAsMainHitbox()
end
function PhysicalActor:addHitbox(name, type, data, sx, sy, isSolid)
isSolid = (isSolid == true)
if (self.hitboxes[name] ~= nil) then
core.debug:logWarn("PhysicalActor", "the hitbox " .. name .. " already exists")
else
@ -222,7 +218,7 @@ function PhysicalActor:updateHitboxes()
end
end
function PhysicalActor:checkHitboxesCollisions(filter)
function PhysicalActor:applyHitboxesCollisions(filter)
for k, v in pairs(self.hitboxes) do
self:applyHitboxCollisions(k, filter)
end
@ -232,12 +228,12 @@ function PhysicalActor:applyHitboxCollisions(name, filter)
local cols, colNumber = {}, 0
local filter = filter or self.filter
if (self.isDestroyed == false) and (self.hitboxes[name] ~= nil) then
cols, colNumber = self.hitboxes[name]:checkCollision(filter)
local type = self.hitboxes[name].type
cols, colNumber = self.hitboxes[name]:checkCollision(filter)
local type = self.hitboxes[name].type
for i, col in ipairs(cols) do
self:hitboxResponse(name, type, col)
end
for i, col in ipairs(cols) do
self:hitboxResponse(name, type, col)
end
end
return cols, colNumber
@ -274,4 +270,4 @@ function PhysicalActor:drawMainHitbox()
end
end
return PhysicalActor
return PhysicalActor

View File

@ -94,4 +94,4 @@ function ShapedActor:redrawShadowCanvas()
end
end
return ShapedActor
return ShapedActor

View File

@ -37,7 +37,7 @@ end
function SpritedActor:changeAnimation(animation, restart)
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:changeAnimation is deprecated, prefer SpritedActor.sprite:changeAnimation()")
core.debug:warning("actor", "the function SpritedActor:changeAnimation is deprecated, prefer SpritedActor.sprite:changeAnimation()")
self.sprite:changeAnimation(animation, restart)
end
end
@ -48,7 +48,7 @@ end
function SpritedActor:setCustomSpeed(customSpeed)
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:setCustomSpeed is deprecated, prefer SpritedActor.sprite:setCustomSpeed()")
core.debug:warning("actor", "the function SpritedActor:setCustomSpeed is deprecated, prefer SpritedActor.sprite:setCustomSpeed()")
self.sprite:setCustomSpeed(customSpeed)
end
end
@ -61,33 +61,33 @@ end
function SpritedActor:getCurrentAnimation()
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:getCurrentAnimation is deprecated, prefer SpritedActor.sprite:getCurrentAnimation()")
core.debug:warning("actor", "the function SpritedActor:getCurrentAnimation is deprecated, prefer SpritedActor.sprite:getCurrentAnimation()")
return self.sprite:getCurrentAnimation()
end
end
function SpritedActor:getSpriteScalling()
core.debug:logWarn("actor", "the function SpritedActor:getSpriteScalling is deprecated, prefer SpritedActor.sprite:getScalling()")
core.debug:warning("actor", "the function SpritedActor:getSpriteScalling is deprecated, prefer SpritedActor.sprite:getScalling()")
return self.sprite:getScalling()
end
function SpritedActor:getFrame()
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:getFrame is deprecated, prefer SpritedActor.sprite:getFrame()")
core.debug:warning("actor", "the function SpritedActor:getFrame is deprecated, prefer SpritedActor.sprite:getFrame()")
return self.sprite:getFrame()
end
end
function SpritedActor:getRelativeFrame()
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:getRelativeFrame is deprecated, prefer SpritedActor.sprite:getRelativeFrame()")
core.debug:warning("actor", "the function SpritedActor:getRelativeFrame is deprecated, prefer SpritedActor.sprite:getRelativeFrame()")
return self.sprite:getRelativeFrame()
end
end
function SpritedActor:getAnimationDuration()
if (self.sprite ~= nil) then
core.debug:logWarn("actor", "the function SpritedActor:getAnimationDuration is deprecated, prefer SpritedActor.sprite:getAnimationDuration()")
core.debug:warning("actor", "the function SpritedActor:getAnimationDuration is deprecated, prefer SpritedActor.sprite:getAnimationDuration()")
return self.sprite:getAnimationDuration()
end
end

View File

@ -23,16 +23,18 @@
local TimedActor = Object:extend()
local TweenManager = require "birb.classes.time"
-- TIMER FUNCTIONS
-- Control the integrated timers of the actor
function TimedActor:initTimers()
self.timers = core.modules.Timers(self)
self.timers = TweenManager(self)
self:addUpdateFunction(self.updateTimers)
end
function TimedActor:addTimer(name, t)
core.debug:logWarn("actor", "function actor:addTimer is deprecated, prefer actor.timers:newTimer")
core.debug:warning("actor", "function actor:addTimer is deprecated, prefer actor.timers:newTimer")
self.timers:newTimer(t, name)
end

View File

@ -37,7 +37,7 @@ end
function MappedBox:drawTextureContent()
local tx, ty = self.x, self.y - (self.z + self.d)
core.debug:logInfo("mappedbox", "getting map layers at position " .. tx .. ";" .. ty)
core.debug:print("mappedbox", "getting map layers at position " .. tx .. ";" .. ty)
love.graphics.push()
love.graphics.origin()
love.graphics.translate(math.floor(-tx), math.floor(-ty))

View File

@ -29,8 +29,8 @@ local TexturedBox = Box3D:extend()
function TexturedBox:new(owner, w, h, d, topTexture, bottomTexture)
local bottomTexture = bottomTexture or topTexture
self.topTexture = owner.assets:getWithType(topTexture, "texture")
self.bottomTexture = owner.assets:getWithType(bottomTexture, "texture")
self.topTexture = owner.assets.images[topTexture]
self.bottomTexture = owner.assets.images[bottomTexture]
TexturedBox.super.new(self, owner, w, h, d)
self.haveLine = false

View File

@ -23,6 +23,7 @@
]]
local Hitbox2D = Object:extend()
local rectStructure = require "birb.structures.rect"
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
@ -42,24 +43,6 @@ function Hitbox2D:new(owner, type, data, sx, sy, isSolid)
self:register()
end
function Hitbox2D:setFromData(data, sx, sy)
local sx = sx or 0
local sy = sy or 0
self.ox = data[1]
self.oy = data[2]
self.w = data[3]
self.h = data[4]
if (sx < 0) then
self.ox = self.owner.w - self.ox - self.w
end
if (sy < 0) then
self.oy = self.owner.h - self.oy - self.h
end
end
function Hitbox2D:advertiseAsMainHitbox()
self.isMainHitBox = true
end
@ -72,6 +55,30 @@ function Hitbox2D:modify(ox, oy, w, h)
self.h = h
end
function Hitbox2D:setFromData(data, sx, sy)
local rect = utils.table.parse(data, rectStructure)
self.ox = rect.x
self.oy = rect.y
self.w = rect.w
self.h = rect.h
self:applyScale(sx, sy)
end
function Hitbox2D:applyScale(sx, sy)
local sx = sx or 1
local sy = sy or 1
if (sx < 0) then
self.ox = self.owner.w - self.ox - self.w
end
if (sy < 0) then
self.oy = self.owner.h - self.oy - self.h
end
end
function Hitbox2D:setDebugColor(r,g,b)
self.debug = {}
self.debug.r = r
@ -123,13 +130,41 @@ end
function Hitbox2D:checkCollisionAtPoint(dx, dy, filter)
self:updatePosition()
local dx, dy = self.ox + dx, self.oy + dy
local x, y, cols, colNumber = self.world:checkCollision(self, dx, dy, filter)
local nx, ny = self.ox + dx, self.oy + dy
if (self.owner.useTileCollision and self.world.map.supportTileCollision) then
if (self:checkTileCollision(nx, self.y)) then
nx = self.x
local relx = dx - self.x
for i = 1, math.ceil(math.abs(relx)), 1 do
local nnx = self.x + i * utils.math.sign(relx)
if (not self:checkTileCollision(nnx, self.y)) then
nx = nnx
end
end
self.owner.xsp = 0
end
if (self:checkTileCollision(self.x, ny)) then
ny = self.y
local rely = dy - self.y
for i = 1, math.ceil(math.abs(rely)), 1 do
local nny = self.y + i * utils.math.sign(rely)
if (not self:checkTileCollision(self.x, nny)) then
ny = nny
end
end
self.owner.ysp = 0
end
end
local x, y, cols, colNumber = self.world:checkCollisionAtPoint(self, nx, ny, filter)
local newx, newy = self:getNewOwnerPosition(x, y)
return newx, newy, cols, colNumber
end
function Hitbox2D:checkTileCollision(dx, dy)
return self.world:haveTileTypeInRect(dx + 1, dy + 1, self.w - 2, self.h - 2, "solid")
end
-- DRAW FUNCTIONS
-- Just some debug function to draw hitbox
@ -137,8 +172,6 @@ function Hitbox2D:draw()
local x, y = self:getPosition()
love.graphics.setColor(self.debug.r, self.debug.g, self.debug.b, 1)
utils.graphics.box(x, y, self.w, self.h)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.points(x, y)
utils.graphics.resetColor()
end

View File

@ -23,6 +23,7 @@
]]
local Hitbox3D = Object:extend()
local boxStructure = require "birb.structures.box"
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
@ -42,25 +43,6 @@ function Hitbox3D:new(owner, type, data, sx, sy, isSolid)
self:register()
end
function Hitbox3D:setFromData(data, sx, sy)
local sx = sx or 0
local sy = sy or 0
self.ox = data[1]
self.oy = data[2]
self.oz = data[3]
self.w = data[4]
self.h = data[5]
self.d = data[6]
if (sx < 0) then
self.ox = self.owner.w - self.ox - self.w
end
if (sy < 0) then
self.oz = self.owner.d - self.oz - self.d
end
end
function Hitbox3D:advertiseAsMainHitbox()
self.isMainHitBox = true
end
@ -75,6 +57,32 @@ function Hitbox3D:modify(ox, oy, oz, w, h, d)
self.d = d
end
function Hitbox3D:setFromData(data, sx, sy)
local box = utils.table.parse(data, boxStructure)
self.ox = box.x
self.oy = box.y
self.oz = box.z
self.w = box.w
self.h = box.h
self.d = box.d
self:applyScale(sx, sy)
end
function Hitbox3D:applyScale(sx, sy)
local sx = sx or 1
local sy = sy or 1
if (sx < 0) then
self.ox = self.owner.w - self.ox - self.w
end
if (sy < 0) then
self.oy = self.owner.h - self.oy - self.h
end
end
function Hitbox3D:setDebugColor(r,g,b)
self.debug = {}
self.debug.r = r
@ -126,8 +134,8 @@ end
function Hitbox3D:checkCollisionAtPoint(dx, dy, dz, filter)
self:updatePosition()
local dx, dy = self.ox + dx, self.oy + dy, self.oz + dz
local x, y, z, cols, colNumber = self.world:checkCollision(self, dx, dy, dz, filter)
local dx, dy, dz = self.ox + dx, self.oy + dy, self.oz + dz
local x, y, z, cols, colNumber = self.world:checkCollisionAtPoint(self, dx, dy, dz, filter)
local newx, newy, newz = self:getNewOwnerPosition(x, y, z)
return newx, newy, newz, cols, colNumber

View File

@ -17,8 +17,7 @@ end
function Sprite:clone()
if self.name ~= nil then
local baseSprite = self.assets:getWithType(self.name, "sprite")
self.spriteClone = baseSprite:clone()
self.spriteClone = self.assets.sprites[self.name]:clone()
self.spriteClone:setCallbackTarget(self.owner)
end
end
@ -49,14 +48,16 @@ function Sprite:update(dt)
end
end
function Sprite:setScalling(sx, sy)
if (sx ~= nil) then
self.sx = sx
end
function Sprite:setScallingX(sx)
local sx = sx or 1
if (sy ~= nil) then
self.sy = sy
end
self.sx = sx
end
function Sprite:setScallingY(sy)
local sy = sy or 1
self.sy = sy
end
function Sprite:getCurrentAnimation()
@ -111,10 +112,9 @@ function Sprite:draw(x, y, r, sx, sy, ox, oy, kx, ky)
if (self.spriteClone ~= nil) then
self.spriteClone:draw(x, y, r, sx, sy, ox, oy, kx, ky)
else
local sprite = self.assets:getWithType(self.name, "sprite")
sprite:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
self.assets.sprites[self.name]:draw(x, y, r, sx, sy, ox, oy, kx, ky)
end
end
end
return Sprite
return Sprite

View File

@ -102,17 +102,23 @@ function BaseWorld:initActors( )
self.currentCreationID = 0
end
function BaseWorld:newActor(name, x, y, z)
function BaseWorld:newActor(name, x, y, z, properties, mapname)
local debugstring = " at (" .. x .. ";" .. y .. ")."
core.debug:logInfo("world2D", "adding actor " .. name .. debugstring)
self.obj.index[name](self, x, y)
core.debug:print("world2D", "adding actor " .. name .. debugstring)
local actor = self.obj.index[name](self, x, y)
if (mapname ~= nil) then
actor.mapname = mapname
end
if (properties ~= nil) then
actor:setProperties(properties)
end
end
function BaseWorld:newCollision(name, x, y, z, w, h, d)
local debugstringpos = "at (" .. x .. ";" .. y .. ")"
local debugstringsize = "size is (" .. w .. ";" .. h .. ")"
local debugstring = " " .. debugstringpos .. ". " .. debugstringsize .. "."
core.debug:logInfo("world2D", "creating collision " .. name .. debugstring)
core.debug:print("world2D", "creating collision " .. name .. debugstring)
self.obj.collisions[name](self, x, y, w, h)
end
@ -202,7 +208,7 @@ function BaseWorld:moveActor(actor, x, y, filter)
return x, y, {}, 0
end
function BaseWorld:checkCollision(actor, x, y, filter)
function BaseWorld:checkCollisionAtPoint(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
@ -252,9 +258,10 @@ function BaseWorld:newPlayer(x, y, z)
end
function BaseWorld:sendInputToPlayers(actor)
for i, player in ipairs(self.players) do
local keys = self.scene:getKeys(player.sourceid)
player.actor:getInput(keys)
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
@ -351,9 +358,11 @@ end
-- All update functions
function BaseWorld:update(dt)
self:updateMap(dt)
self:sendInputToPlayers(dt)
self:updateActors(dt)
if (core.screen:isActive()) then
self:updateMap(dt)
self:sendInputToPlayers(dt)
self:updateActors(dt)
end
self.cameras:update(dt)
end
@ -370,6 +379,19 @@ function BaseWorld:updateMap(dt)
end
end
function BaseWorld:getTileTypeAtPoint(x, y)
if (self.map.getTileTypeAtPoint ~= nil) then
return self.map:getTileTypeAtPoint(x, y)
end
end
function BaseWorld:haveTileTypeInRect(x, y, x2, y2, type)
if (self.map.haveTileTypeInRect ~= nil) then
return self.map:haveTileTypeInRect(x, y, x2, y2, type)
end
end
-- DRAW FUNCTIONS
-- All function to draw the map, world and actors
@ -378,13 +400,15 @@ function BaseWorld:draw(dt)
local camNumber = self.cameras:getViewNumber()
if (camNumber == 0) then
self:drawMap()
self:drawLowerLayers()
self:drawActors()
self:drawUpperLayers()
else
for i=1, camNumber do
self.cameras:attachView(i)
self:drawMap(i)
self:drawLowerLayers(i)
self:drawActors(i)
self:drawUpperLayers(i)
self.cameras:detachView(i)
end
end
@ -398,6 +422,18 @@ function BaseWorld:drawActors(id)
end
end
function BaseWorld:drawUpperLayers()
if (self.map ~= nil) then
self.map:drawUpperLayers()
end
end
function BaseWorld:drawLowerLayers()
if (self.map ~= nil) then
self.map:drawLowerLayers()
end
end
function BaseWorld:drawMap(id)
if (self.map ~= nil) then
self.map:draw()

Some files were not shown because too many files have changed in this diff Show More