Compare commits

...

11 Commits

Author SHA1 Message Date
Jill 215aab41d3
stdlib: fix event system switch 2023-05-04 22:07:28 +03:00
Jill 1bc3604f9f
stdlib.easable: rewrite 2023-05-04 21:57:54 +03:00
Jill 6a0cb734db
stdlib.aft: aftSetup for convenience 2023-05-04 21:56:26 +03:00
Jill b34c18da8a
config library 2023-05-04 21:39:13 +03:00
Jill e8cb599dde
split code a lil better 2023-05-04 21:03:10 +03:00
Jill 3ceba9bafc
ignore result of main.lua 2023-05-04 20:26:41 +03:00
Jill 78f78c96db
avoid leaking uranium stuff into globals 2023-05-04 20:25:09 +03:00
Jill 9821df6c2e
move some typings over to the actual files 2023-05-04 18:10:14 +03:00
Jill dd7234cc9e
swap to more Normal event system layout 2023-05-04 17:36:49 +03:00
Jill 57225d526c
split into modules a lil 2023-05-04 17:29:26 +03:00
Jill db7d0db7ec
switch template to lua files 2023-05-04 16:43:42 +03:00
17 changed files with 966 additions and 960 deletions

794
main.xml
View File

@ -1,7 +1,6 @@
<Layer Type="ActorFrame" InitCommand="%function(self) <Layer Type="ActorFrame" InitCommand="%function(self)
_G.oat = {} _G.oat = {}
oat._main = self oat._main = self
oat.dir = GAMESTATE:GetCurrentSong():GetSongDir()
setmetatable(oat, { setmetatable(oat, {
-- if something isn't found in the table, fall back to a global lookup -- if something isn't found in the table, fall back to a global lookup
@ -14,6 +13,9 @@
end end
}) })
uranium = {}
uranium.dir = GAMESTATE:GetCurrentSong():GetSongDir()
-- make require work -- make require work
-- stolen from mirin template -- stolen from mirin template
-- https://github.com/XeroOl/notitg-mirin/blob/0fbff2ee93d905feeb58c4aac4fe7f5f9ebc9647/template/std.lua#L17 -- https://github.com/XeroOl/notitg-mirin/blob/0fbff2ee93d905feeb58c4aac4fe7f5f9ebc9647/template/std.lua#L17
@ -33,7 +35,7 @@
local filename = string.gsub(modname, '%.', '/') local filename = string.gsub(modname, '%.', '/')
for path in (string.gfind or string.gmatch)(oat.package.path, '[^;]+') do for path in (string.gfind or string.gmatch)(oat.package.path, '[^;]+') do
-- get the file path -- get the file path
local filepath = oat.dir .. string.gsub(path, '%?', filename) local filepath = uranium.dir .. string.gsub(path, '%?', filename)
-- check if file exists -- check if file exists
if not GAMESTATE:GetFileStructure(filepath) then if not GAMESTATE:GetFileStructure(filepath) then
table.insert(errors, 'no file \''..filepath..'\'') table.insert(errors, 'no file \''..filepath..'\'')
@ -79,792 +81,10 @@
oat() oat()
local function copy(src) require 'uranium.main'
local dest = {}
for k, v in pairs(src) do
dest[k] = v
end
return dest
end
oat.oat = _G.oat -- Needed by StepMania, in order to not kill lua mods early
oat.type = _G.type self:sleep(9e9)
oat.print = _G.print
oat.pairs = _G.pairs
oat.ipairs = _G.ipairs
oat.unpack = _G.unpack
oat.tonumber = _G.tonumber
oat.tostring = _G.tostring
oat.math = copy(_G.math)
oat.table = copy(_G.table)
oat.string = copy(_G.string)
oat.scx = SCREEN_CENTER_X
oat.scy = SCREEN_CENTER_Y
oat.sw = SCREEN_WIDTH
oat.sh = SCREEN_HEIGHT
oat.dw = DISPLAY:GetDisplayWidth()
oat.dh = DISPLAY:GetDisplayHeight()
oat.useProfiler = false
oat.profilerInfo = {}
local resetOnFrameStartCfg = false
local resetOnFrameStartActors = {}
local uraniumFunc = {}
local debugCache = {}
function uraniumFunc:call(event, ...)
if self._callbacks[event] then
profilerInfo[event] = {}
for _, callback in ipairs(self._callbacks[event]) do
local start = os.clock()
local res = callback(unpack(arg))
local dur = os.clock() - start
if oat.useProfiler then
if not debugCache[callback] then
debugCache[callback] = debug.getinfo(callback, 'Sl') -- cached cus debug.getinfo is EXPENSIVE
end
local finfo = debugCache[callback]
table.insert(profilerInfo[event], {
src = finfo.short_src .. ':' .. finfo.linedefined,
t = dur
})
end
if res ~= nil then return res end
end
end
end
local uraniumMeta = {}
function uraniumMeta:__newindex(key, value)
if self._callbacks[key] then
table.insert(self._callbacks[key], value)
else
self._callbacks[key] = {value}
end
end
uraniumMeta.__index = uraniumFunc
uranium = setmetatable({_callbacks = {}}, uraniumMeta)
function backToSongWheel(message)
if message then
SCREENMAN:SystemMessage(message)
print(message)
end
GAMESTATE:FinishSong()
-- disable update_command
self:hidden(1)
end
local hasExited = false
local function exit()
if hasExited then return end
hasExited = true
uranium:call('exit')
-- good templates clean up after themselves
uranium = nil
_G.oat = nil
oat = nil
_main:hidden(1)
collectgarbage()
end
local actorsInitialized = false -- if true, no new actors can be created
local actorsInitializing = false -- the above but a bit more explicit
local luaobj
local globalQueue = {} -- for resetting
local patchedFunctions = {}
local function patchFunction(f, obj)
if not patchedFunctions[f] then patchedFunctions[f] = {} end
if not patchedFunctions[f][obj] then
patchedFunctions[f][obj] = function(...)
arg[1] = obj
local results
local status, result = pcall(function()
-- doing it this way instead of returning because lua
-- offers no way of grabbing everything BUT the first
-- argument out of pcall
results = {f(unpack(arg))}
end)
if not status then
error(result, 2)
else
return unpack(results)
end
end
end
return patchedFunctions[f][obj]
end
local function onCommand(self)
actorsInitialized = true
actorsInitializing = false
local resetOnFrameStartActors_ = {}
for k,v in pairs(resetOnFrameStartActors) do
resetOnFrameStartActors_[k.__raw] = v
end
resetOnFrameStartActors = resetOnFrameStartActors_
uranium:call('init')
end
function reset(actor)
if not actorsInitialized then error('uranium: cannot reset an actor during initialization', 2) end
for _, q in ipairs(globalQueue) do
local queueActor = q[1]
if queueActor == actor.__raw then
local v = q[2]
local func = queueActor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
patchFunction(func, queueActor)(unpack(v[2]))
end
end
end
end
resetActor = reset
-- runs once during ScreenReadyCommand, before the user code is loaded
-- hides various actors that are placed by the theme
local function hideThemeActors()
for _, element in ipairs {
'Overlay', 'Underlay',
'ScoreP1', 'ScoreP2',
'LifeP1', 'LifeP2',
'PlayerOptionsP1', 'PlayerOptionsP2', 'SongOptions',
'LifeFrame', 'ScoreFrame',
'DifficultyP1', 'DifficultyP2',
'BPMDisplay',
'MemoryCardDisplayP1', 'MemoryCardDisplayP2'
} do
local child = SCREENMAN(element)
if child then child:hidden(1) end
end
end
GAMESTATE:ApplyModifiers('clearall')
local drawfunctionArguments = {}
local specialActorFrames = {} -- ones defined specifically; here for drawfunction jank
function setDrawFunction(frame, func)
--if not frame.__raw then error('uranium: cannot set actorframe drawfunction during module loadtime! put this in uranium.init or actor:addcommand(\'Init\', ...)', 2) end
if not frame.SetDrawFunction then error('uranium: expected an actorframe but got something that doesn\'t even bother to implement SetDrawFunction', 2) end
if type(func) ~= 'function' then error('uranium: tried to set a drawfunction to a.. ' .. type(func) .. '?? the hell', 2) end
frame:SetDrawFunction(function()
for i = 1, frame:GetNumChildren() do
local a = frame:GetChildAt(i - 1)
if specialActorFrames[a] == false then
a:Draw()
end
end
local args = drawfunctionArguments[frame]
if args then
func(unpack(args))
else
func()
end
end)
end
function setShader(actor, shader)
if not shader.__raw then
function uranium.init() setShader(actor, shader) end
else
actor:SetShader(shader.__raw)
end
end
function setShaderfuck(shader)
if not shader.__raw then
function uranium.init() setShaderfuck(shader) end
else
DISPLAY:ShaderFuck(shader.__raw)
end
end
function clearShaderfuck()
DISPLAY:ClearShaderFuck()
end
function resetOnFrameStart(bool)
resetOnFrameStartCfg = bool
end
function resetActorOnFrameStart(actor, bool)
if bool == nil then bool = not resetOnFrameStartCfg end
resetOnFrameStartActors[actor.__raw or actor] = bool
end
local actorAssociationTable = {}
function getChildren(frame)
local c = actorAssociationTable[frame]
if c then
return c
else
error('uranium: actorframe doesn\'t exist (or isn\'t an actorframe)', 2)
end
end
-- actors
local actorQueue = {}
local actorAssociationQueue = {}
local actorTree = {}
local currentPath
local pastPaths = {}
local currentActor = nil
local function findFirstActor(path)
for i, v in ipairs(path) do
if v.type or v.file then
return v, i
end
end
end
local function findFirstActorFrame(path)
for i, v in ipairs(path) do
if not v.type and not v.file then
return v, i
end
end
end
oat._actor = {}
local function nextActor()
local new, idx = findFirstActor(currentPath)
if not new then
currentActor = nil
else
currentActor = new
table.remove(currentPath, idx)
end
end
function oat._actor.recurse(forceActor)
local newFrame, idx = findFirstActorFrame(currentPath)
local newActor = findFirstActor(currentPath)
if newFrame and not (newActor and forceActor) then
table.insert(pastPaths, currentPath)
currentPath = currentPath[idx]
table.remove(pastPaths[#pastPaths], idx)
return true
elseif newActor then
table.insert(pastPaths, currentPath)
return true
else
return false
end
end
function oat._actor.recurseLast()
return oat._actor.recurse(true)
end
function oat._actor.endRecurse()
currentPath = table.remove(pastPaths, #pastPaths)
end
function oat._actor.cond()
return currentActor ~= nil
end
function oat._actor.hasShader()
return oat._actor.cond() and (currentActor.frag ~= nil or currentActor.vert ~= nil)
end
function oat._actor.noShader()
nextActor()
return oat._actor.cond() and not oat._actor.hasShader()
end
function oat._actor.type()
return currentActor.type
end
function oat._actor.file()
return currentActor.file
end
function oat._actor.frag()
return currentActor.frag or 'nop.frag'
end
function oat._actor.vert()
return currentActor.vert or 'nop.vert'
end
function oat._actor.font()
return currentActor.font
end
function oat._actor.init(self)
currentActor.init(self)
self:removecommand('Init')
currentActor = nil -- to prevent any weirdness
end
function oat._actor.initFrame(self)
self:removecommand('Init')
self:SetDrawFunction(function()
for i = 1, self:GetNumChildren() do
local a = self:GetChildAt(i - 1)
if specialActorFrames[a] == false then
a:Draw()
end
end
end)
if currentPath.init then
currentPath.init(self)
currentPath.init = nil
specialActorFrames[self] = true
else
specialActorFrames[self] = false
end
end
local actorMethodOverrides = {
Draw = function(self, ...)
drawfunctionArguments[self] = arg
self.__raw:Draw()
end
}
local function createProxyActor(name)
local queue = {}
local initCommands = {}
local lockedActor
local queueRepresentation
return setmetatable({}, {
__index = function(self, key)
if key == '__raw' then
return lockedActor
end
if lockedActor then
if actorMethodOverrides[key] then
return actorMethodOverrides[key]
else
local val = lockedActor[key]
if type(val) == 'function' then
return patchFunction(val, lockedActor)
end
return val
end
end
if key == '__queue' then
return queueRepresentation
end
if key == '__queueRepresentation' then
return function(q)
queueRepresentation = q
end
end
if key == '__lock' then
return function(actor)
if lockedActor then return end
for _, v in ipairs(queue) do
local func = actor[v[1]]
if not func then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
'you\'re calling a function \'' .. v[1] .. '\' on a ' .. name .. ' which doesn\'t exist!:\n'
)
else
local success, result = pcall(function()
patchFunction(func, actor)(unpack(v[2]))
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
end
-- now that we know there's no poisonous methods in queue, let's offload them
for _, v in ipairs(queue) do
table.insert(globalQueue, {actor, v})
end
-- let's also properly route everything from the proxied actor to the actual actor
lockedActor = actor
-- and now let's run the initcommands
for _, c in ipairs(initCommands) do
local func = c[1]
local success, result = pcall(function()
func(actor)
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' InitCommand defined on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
-- to make mr. Garbage Collector's job easier
initCommands = {}
queueRepresentation = nil
queue = {}
end
else
return function(...)
if actorsInitialized then return end
if key == 'addcommand' and arg[2] == 'Init' then
table.insert(initCommands, {arg[3], debug.getinfo(2, 'Sl')})
else
table.insert(queue, {key, arg, debug.getinfo(2, 'Sl')})
end
end
end
end,
__newindex = function()
error('uranium: cannot set properties on actors!', 2)
end,
__tostring = function() return 'Proxy of ' .. name end,
__name = name
})
end
local function createGenericFunc(type)
return function()
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor(type)
table.insert(actorQueue, {
type = type,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
end
Quad = createGenericFunc('Quad')
ActorProxy = createGenericFunc('ActorProxy')
Polygon = createGenericFunc('Polygon')
ActorFrameTexture = createGenericFunc('ActorFrameTexture')
function Sprite(file)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
--if not file then error('uranium: cannot create a Sprite without a file', 2) end
local actor = createProxyActor('Sprite')
local type = nil
if not file then type = 'Sprite' end
table.insert(actorQueue, {
type = type,
file = file and oat.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function ActorFrame()
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('ActorFrame')
table.insert(actorQueue, {
type = 'ActorFrame',
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
actorAssociationTable[actor] = {}
return actor
end
local function isShaderCode(str)
return string.find(str or '', '\n')
end
function Shader(frag, vert)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('RageShaderProgram')
local fragFile = frag
local vertFile = vert
local isFragShaderCode = isShaderCode(frag)
local isVertShaderCode = isShaderCode(vert)
if isFragShaderCode then fragFile = nil end
if isVertShaderCode then vertFile = nil end
if (frag and vert) and ((isFragShaderCode and not isVertShaderCode) or (not isFragShaderCode and isVertShaderCode)) then
error('uranium: cannot create a shader with 1 shader file and 1 shader code block', 2)
end
table.insert(actorQueue, {
type = 'Sprite',
frag = fragFile and ('../' .. fragFile) or 'nop.frag',
vert = vertFile and ('../' .. vertFile) or 'nop.vert',
init = function(a)
a:hidden(1)
actor.__lock(a:GetShader())
-- shader code stuff
if isFragShaderCode or isVertShaderCode then
a:GetShader():compile(vert or '', frag or '')
end
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function Texture(file)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create a texture without a file', 2) end
local actor = createProxyActor('RageTexture')
table.insert(actorQueue, {
file = file and oat.dir .. file,
init = function(a)
a:hidden(1)
actor.__lock(a:GetTexture())
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function Model(file)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create a Model without a file', 2) end
local actor = createProxyActor('Model')
table.insert(actorQueue, {
type = nil,
file = file and oat.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function BitmapText(font, text)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('BitmapText')
table.insert(actorQueue, {
type = 'BitmapText',
font = font or 'common',
init = function(a)
if text then a:settext(text) end
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function ActorSound(file)
if actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create an ActorSound without a file', 2) end
local actor = createProxyActor('ActorSound')
table.insert(actorQueue, {
type = 'ActorSound',
file = oat.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(actorQueue[#actorQueue])
return actor
end
function addChild(frame, actor)
if not frame or not actor then
error('uranium: frame and actor must both Exist', 2)
end
if actorsInitializing then
error('uranium: cannot create frame-child associations during actor initialization', 2)
end
if actorsInitialized then
error('uranium: cannot create frame-child associations after actors have been initialized', 2)
end
if not frame.__lock then
error('uranium: ActorFrame passed into addChild must be one instantiated with ActorFrame()!', 2)
end
if not actor.__lock then
error('uranium: trying to add a child to an ActorFrame that isn\'t an actor; please read the first half of \'ActorFrame\'', 2)
end
actorAssociationQueue[actor.__queue] = frame.__queue
table.insert(actorAssociationTable[frame], actor)
end
local function transformQueueToTree()
local tree = {}
local paths = {}
local iter = 0
while #actorQueue > 0 do
iter = iter + 1
if iter > 99999 then
error('uranium: failed to transform queue to tree: reached maximum iteration limit! is there an actor with an invalid actorframe?')
end
for i = #actorQueue, 1, -1 do
v = actorQueue[i]
local insertInto
if not actorAssociationQueue[v] then
insertInto = tree
else
if paths[actorAssociationQueue[v]] then
insertInto = paths[actorAssociationQueue[v]]
end
end
if insertInto then
if v.type == 'ActorFrame' then
table.insert(insertInto, {init = v.init})
table.remove(actorQueue, i)
paths[v] = insertInto[#insertInto]
else
table.insert(insertInto, v)
table.remove(actorQueue, i)
end
end
end
end
actorTree = tree
end
local lastt = GAMESTATE:GetSongTime()
local function screenReadyCommand(self)
hideThemeActors()
self:hidden(0)
oat._actor = {}
actorQueue = {}
actorAssociationQueue = {}
actorTree = {}
currentPath = nil
pastPaths = {}
currentActor = nil
collectgarbage()
local errored = false
local firstrun = true
local playersLoaded = false
self:addcommand('Update', function()
if errored then
return 0
end
errored = true
if P1 and P2 then
playersLoaded = true
end
if playersLoaded and not P1 and not P2 then -- sora exit hack
exit()
end
t = os.clock()
b = GAMESTATE:GetSongBeat()
local dt = t - lastt
lastt = t
if firstrun then
firstrun = false
dt = 0
self:GetChildren()[2]:hidden(1)
uranium:call('ready')
end
drawfunctionArguments = {}
for _, q in ipairs(globalQueue) do
local enabled = resetOnFrameStartCfg
local actor = q[1]
local v = q[2]
local pref = resetOnFrameStartActors[actor]
if pref ~= nil then enabled = pref end
if enabled then
local func = actor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
patchFunction(func, actor)(unpack(v[2]))
end
end
end
uranium:call('preUpdate', dt)
uranium:call('update', dt)
uranium:call('postUpdate', dt)
errored = false
return 0
end)
self:luaeffect('Update')
end
if not pcall(function() oat._release = require('release') end) then
oat._release = require('release_blank')
end
local success, result = pcall(function()
require('main')
end)
if success then
luaobj = result
print('---')
actorsInitializing = true
transformQueueToTree()
--Trace(fullDump(actorTree))
currentPath = actorTree
self:addcommand('On', onCommand)
self:addcommand('Ready', screenReadyCommand)
self:addcommand('Off', exit)
self:addcommand('SaltyReset', exit)
self:addcommand('WindowFocus', function()
uranium:call('focus', true)
end)
self:addcommand('WindowFocusLost', function()
uranium:call('focus', false)
end)
self:queuecommand('Ready')
else
Trace('got an error loading main.lua!')
Trace(result)
backToSongWheel('loading .lua file failed, check log for details')
error('uranium: loading main.lua file failed:\n' .. result)
end
-- NotITG and OpenITG have a long standing bug where the InitCommand on an actor can run twice in certain cases. -- NotITG and OpenITG have a long standing bug where the InitCommand on an actor can run twice in certain cases.
-- By removing the command after it's done, it can only ever run once -- By removing the command after it's done, it can only ever run once

View File

@ -17,4 +17,15 @@ function self.aft(self)
self:Create() self:Create()
end end
function self.aftSetup()
local a = ActorFrameTexture()
local b = Sprite()
uranium.on('init', function()
self.aft(a)
self.sprite(b)
b:SetTexture(a:GetTexture())
end)
return a, b
end
return self return self

View File

@ -1,72 +1,42 @@
require('stdlib.util')
---@class easable ---@class easable
---@field public a number @the eased value ---@field eased number | any
---@field public toa number @the target, uneased value ---@field target number | any
local eas = {} ---@field speed number
local easable = {}
---@param new number @New value to ease to --- move towards a new target
---@return void function easable:set(n)
function eas:set(new) self.target = n
self.toa = new
end end
---@param new number @New value --- move towards a new target additively
---@return void function easable:add(n)
function eas:reset(new) self.target = self.target + n
self.toa = new
self.a = new
end end
---@param new number @How much to add to current value to ease to --- set both the eased value and the target
---@return void function easable:reset(n)
function eas:add(new) self.target = n
self.toa = self.toa + new self.eased = n
end end
local easmeta = {} ---@param dt number
function easable:update(dt)
easmeta.__index = eas self.eased = math.pow(self.speed, -dt) * (self.eased - self.target) + self.target
easmeta.__name = 'easable'
function easmeta.__add(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) + ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__sub(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) - ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mul(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) * ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__div(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) / ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mod(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) % ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__eq(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) == ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__lt(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) < ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__le(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) <= ((type(b) == 'table' and b.a) and b.a or b)
end end
function easmeta:__call(dt) function easable:__tostring()
self.a = mix(self.a, self.toa, dt) return 'easable (' .. self.eased .. ' towards ' .. self.target .. ')'
end
function easmeta:__tostring()
return tostring(self.a)
end
function easmeta:__unm(self)
return -self.a
end end
---@param default number easable.__index = easable
easable.__name = 'easable'
---@return easable ---@return easable
return function(default) return function(default, speed)
default = default or 0 return setmetatable({
return setmetatable({a = default, toa = default}, easmeta) eased = default,
target = default,
speed = speed and math.pow(2, speed) or 2
}, easable)
end end

View File

@ -8,9 +8,9 @@ return function()
P2:SetNoteDataFromLua({}) P2:SetNoteDataFromLua({})
end end
function uranium.update() uranium.on('update', function()
if b >= 1 then if b >= 1 then
GAMESTATE:SetSongBeat(b % 1) GAMESTATE:SetSongBeat(b % 1)
end end
end end)
end end

View File

@ -106,7 +106,7 @@ function self.isDown(i, pn)
return self.getInput(i, pn) ~= -1 return self.getInput(i, pn) ~= -1
end end
function uranium.init() uranium.on('init', function()
for pn = 1, 2 do for pn = 1, 2 do
for j, v in pairs(self.inputType) do for j, v in pairs(self.inputType) do
local j = j -- lua scope funnies local j = j -- lua scope funnies
@ -124,6 +124,6 @@ function uranium.init()
end) end)
end end
end end
end end)
return self return self

View File

@ -14,6 +14,6 @@ require('stdlib.mirin.sort')
require('stdlib.mirin.ease') require('stdlib.mirin.ease')
require('stdlib.mirin.template') require('stdlib.mirin.template')
function uranium.init() uranium.on('init', function()
xero.init_command(xeroActorsAF) xero.init_command(xeroActorsAF)
end end)

View File

@ -1,14 +1,14 @@
local oldAutoplay local oldAutoplay
return function() return function()
function uranium.ready() uranium.on('ready', function()
oldAutoplay = PREFSMAN:GetPreference('AutoPlay') oldAutoplay = PREFSMAN:GetPreference('AutoPlay')
PREFSMAN:SetPreference('AutoPlay', 0) PREFSMAN:SetPreference('AutoPlay', 0)
end end)
function uranium.exit() uranium.on('exit', function()
if oldAutoplay and oldAutoplay ~= 0 then if oldAutoplay and oldAutoplay ~= 0 then
PREFSMAN:SetPreference('AutoPlay', oldAutoplay) PREFSMAN:SetPreference('AutoPlay', oldAutoplay)
end end
end end)
end end

View File

@ -49,8 +49,8 @@ if PROFILER_ENABLED then
text:Draw() text:Draw()
end end
function uranium.postUpdate(dt) uranium.on('postUpdate', function(dt)
max(dt * 12) max(dt * 12)
draw() draw()
end end)
end end

View File

@ -176,18 +176,18 @@ function self.getLastSave()
end end
end end
function uranium.init() uranium.on('init', function()
loadedSavedata = true loadedSavedata = true
if savedataName then if savedataName then
self.load() self.load()
end end
end end)
function self.enableAutosave() function self.enableAutosave()
checkIfInitialized() checkIfInitialized()
function uranium.exit() uranium.on('exit', function()
self.save(true) self.save(true)
end end)
end end
return self return self

View File

@ -40,7 +40,7 @@ function self:unscheduleInTicks(i)
scheduledTicks[i] = nil scheduledTicks[i] = nil
end end
function uranium.update(dt) uranium.on('update', function(dt)
for k, s in pairs(scheduledTicks) do for k, s in pairs(scheduledTicks) do
s[1] = s[1] - 1 s[1] = s[1] - 1
if s[1] <= 0 then if s[1] <= 0 then
@ -56,6 +56,6 @@ function uranium.update(dt)
scheduled[k] = nil scheduled[k] = nil
end end
end end
end end)
return self return self

View File

@ -1,7 +1,7 @@
---@meta ---@meta
-- cleaning up some notitg typing jank... ehe -- cleaning up some notitg typing jank... ehe
---@alias int number ---@alias int integer
---@alias float number ---@alias float number
---@alias Quad Actor ---@alias Quad Actor
---@alias void nil ---@alias void nil
@ -16,82 +16,6 @@ PROFILEMAN = {}
---@type RageInput ---@type RageInput
INPUTMAN = {} INPUTMAN = {}
---@return Quad
--- Defines a Quad actor.
function Quad() end
---@return ActorProxy
--- Defines an ActorProxy actor.
function ActorProxy() end
---@return Polygon
--- Defines a Polygon actor.
function Polygon() end
---@param file string | nil
---@return Sprite
--- Defines a Sprite actor.
function Sprite(file) end
---@param file string
---@return RageTexture
--- Defines a texture.
function Texture(file) end
---@param file string
---@return Model
--- Defines a Model actor.
function Model(file) end
---@param font string?
---@param text string?
---@return BitmapText
--- Defines a BitmapText actor.
function BitmapText(font, text) end
---@param file string
---@return ActorSound
--- Defines an ActorSound actor.
function ActorSound(file) end
---@return ActorFrameTexture
--- Defines an ActorFrameTexture actor.
function ActorFrameTexture() end
---@param frag string | nil
---@param vert string | nil
---@return RageShaderProgram
--- Defines a shader. `frag` and `vert` can either be filenames or shader code.
function Shader(frag, vert) end
---@return ActorFrame
---@see addChild
--- Defines an ActorFrame. Add children to it with `addChild`.
function ActorFrame() end
---@param actor Actor
--- Resets an actor to its initial state
function reset(actor) end
resetActor = reset
---@param frame ActorFrame
---@param actor Actor
--- Adds a child to an ActorFrame. **Please be aware of the side-effects!**
function addChild(frame, actor) end
---@param frame ActorFrame
---@param func function
--- SetDrawFunction with special behavior to account for Uranium's actor loading scheme.
function setDrawFunction(frame, func) end
---@param actor Actor
---@param shader RageShaderProgram
function setShader(actor, shader) end
-- Toggle actor resetting on frame start behavior by default.
---@param bool boolean
function resetOnFrameStart(bool) end
-- Toggle actor resetting on frame start for individual actors. `bool` defaults to the opposite of your `resetOnFrameStart` config
---@param actor Actor
---@param bool boolean | nil
function resetActorOnFrameStart(actor, bool) end
-- Gets every child of an ActorFrame. More accurate than :GetChildren()
---@param frame ActorFrame
---@return Actor[]
function getChildren(frame) end
---@type number ---@type number
--- A simple timer. Ticks upwards at a rate of 1/sec. --- A simple timer. Ticks upwards at a rate of 1/sec.
--- ---
@ -127,26 +51,11 @@ dw = 0
--- The display height. --- The display height.
dh = 0 dh = 0
--- The Uranium Template table! Mostly callback-related stuff goes here.
uranium = {}
--- A callback for initialization. Called on `OnCommand`.
uranium.init = function() end
--- A callback for updates. Called every frame. Draw stuff here!
uranium.update = function() end
---@param event string
---@param ... any
---@return any
--- Call a defined callback.
function uranium:call(event, ...) end
--- Equivalent to a modfile-sandboxed `_G`, similar to Mirin's `xero`. You shouldn't need this; and if you do, *what are you doing?* --- Equivalent to a modfile-sandboxed `_G`, similar to Mirin's `xero`. You shouldn't need this; and if you do, *what are you doing?*
oat = _G oat = _G
---@class ProfilerInfo --- The Uranium Template table! All template-related functionality is stored here.
---@field public t number uranium = {}
---@field public src string ---@type string
--- A shorthand for `GAMESTATE:GetCurrentSong():GetSongDir()`.
---@type table<string, ProfilerInfo> uranium.dir = nil
profilerInfo = {}

601
uranium/actors.lua Normal file
View File

@ -0,0 +1,601 @@
local M = {}
M._actorsInitialized = false -- if true, no new actors can be created
M._actorsInitializing = false -- the above but a bit more explicit
local drawfunctionArguments = {}
local specialActorFrames = {} -- ones defined specifically; here for drawfunction jank
---@param frame ActorFrame
---@param func function
--- SetDrawFunction with special behavior to account for Uranium's actor loading scheme.
function setDrawFunction(frame, func)
--if not frame.__raw then error('uranium: cannot set actorframe drawfunction during module loadtime! put this in uranium.init or actor:addcommand(\'Init\', ...)', 2) end
if not frame.SetDrawFunction then error('uranium: expected an actorframe but got something that doesn\'t even bother to implement SetDrawFunction', 2) end
if type(func) ~= 'function' then error('uranium: tried to set a drawfunction to a.. ' .. type(func) .. '?? the hell', 2) end
frame:SetDrawFunction(function()
for i = 1, frame:GetNumChildren() do
local a = frame:GetChildAt(i - 1)
if specialActorFrames[a] == false then
a:Draw()
end
end
local args = drawfunctionArguments[frame]
if args then
func(unpack(args))
else
func()
end
end)
end
---@param actor Actor
---@param shader RageShaderProgram
function setShader(actor, shader)
if not shader.__raw then
uranium.on('init', function() setShader(actor, shader) end)
else
actor:SetShader(shader.__raw)
end
end
function setShaderfuck(shader)
if not shader.__raw then
uranium.on('init', function() setShaderfuck(shader) end)
else
DISPLAY:ShaderFuck(shader.__raw)
end
end
function clearShaderfuck()
DISPLAY:ClearShaderFuck()
end
oat._actorAssociationTable = {}
-- Gets every child of an ActorFrame. More accurate than :GetChildren()
---@param frame ActorFrame
---@return Actor[]
function getChildren(frame)
local c = oat._actorAssociationTable[frame]
if c then
return c
else
error('uranium: actorframe doesn\'t exist (or isn\'t an actorframe)', 2)
end
end
local patchedFunctions = {}
function oat._patchFunction(f, obj)
if not patchedFunctions[f] then patchedFunctions[f] = {} end
if not patchedFunctions[f][obj] then
patchedFunctions[f][obj] = function(...)
arg[1] = obj
local results
local status, result = pcall(function()
-- doing it this way instead of returning because lua
-- offers no way of grabbing everything BUT the first
-- argument out of pcall
results = {f(unpack(arg))}
end)
if not status then
error(result, 2)
else
return unpack(results)
end
end
end
return patchedFunctions[f][obj]
end
M._globalQueue = {} -- for resetting
---@param actor Actor
--- Resets an actor to its initial state
function reset(actor)
if not M._actorsInitialized then error('uranium: cannot reset an actor during initialization', 2) end
for _, q in ipairs(M._globalQueue) do
local queueActor = q[1]
if queueActor == actor.__raw then
local v = q[2]
local func = queueActor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
oat._patchFunction(func, queueActor)(unpack(v[2]))
end
end
end
end
resetActor = reset
M._actorQueue = {}
M._actorAssociationQueue = {}
M._actorTree = {}
M._currentPath = nil
M._pastPaths = {}
M._currentActor = nil
local function findFirstActor(path)
for i, v in ipairs(path) do
if v.type or v.file then
return v, i
end
end
end
local function findFirstActorFrame(path)
for i, v in ipairs(path) do
if not v.type and not v.file then
return v, i
end
end
end
oat._actor = {}
local function nextActor()
local new, idx = findFirstActor(M._currentPath)
if not new then
M._currentActor = nil
else
M._currentActor = new
table.remove(M._currentPath, idx)
end
end
function oat._actor.recurse(forceActor)
local newFrame, idx = findFirstActorFrame(M._currentPath)
local newActor = findFirstActor(M._currentPath)
if newFrame and not (newActor and forceActor) then
table.insert(M._pastPaths, M._currentPath)
M._currentPath = M._currentPath[idx]
table.remove(M._pastPaths[#M._pastPaths], idx)
return true
elseif newActor then
table.insert(M._pastPaths, M._currentPath)
return true
else
return false
end
end
function oat._actor.recurseLast()
return oat._actor.recurse(true)
end
function oat._actor.endRecurse()
M._currentPath = table.remove(M._pastPaths, #M._pastPaths)
end
function oat._actor.cond()
return M._currentActor ~= nil
end
function oat._actor.hasShader()
return oat._actor.cond() and (M._currentActor.frag ~= nil or M._currentActor.vert ~= nil)
end
function oat._actor.noShader()
nextActor()
return oat._actor.cond() and not oat._actor.hasShader()
end
function oat._actor.type()
return M._currentActor.type
end
function oat._actor.file()
return M._currentActor.file
end
function oat._actor.frag()
return M._currentActor.frag or 'nop.frag'
end
function oat._actor.vert()
return M._currentActor.vert or 'nop.vert'
end
function oat._actor.font()
return M._currentActor.font
end
function oat._actor.init(self)
M._currentActor.init(self)
self:removecommand('Init')
M._currentActor = nil -- to prevent any weirdness
end
function oat._actor.initFrame(self)
self:removecommand('Init')
self:SetDrawFunction(function()
for i = 1, self:GetNumChildren() do
local a = self:GetChildAt(i - 1)
if specialActorFrames[a] == false then
a:Draw()
end
end
end)
if M._currentPath.init then
M._currentPath.init(self)
M._currentPath.init = nil
specialActorFrames[self] = true
else
specialActorFrames[self] = false
end
end
local actorMethodOverrides = {
Draw = function(self, ...)
drawfunctionArguments[self] = arg
self.__raw:Draw()
end
}
-- todo: probably make this more sane
local function createProxyActor(name)
local queue = {}
local initCommands = {}
local lockedActor
local queueRepresentation
return setmetatable({}, {
__index = function(self, key)
if key == '__raw' then
return lockedActor
end
if lockedActor then
if actorMethodOverrides[key] then
return actorMethodOverrides[key]
else
local val = lockedActor[key]
if type(val) == 'function' then
return oat._patchFunction(val, lockedActor)
end
return val
end
end
if key == '__queue' then
return queueRepresentation
end
if key == '__queueRepresentation' then
return function(q)
queueRepresentation = q
end
end
if key == '__lock' then
return function(actor)
if lockedActor then return end
for _, v in ipairs(queue) do
local func = actor[v[1]]
if not func then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
'you\'re calling a function \'' .. v[1] .. '\' on a ' .. name .. ' which doesn\'t exist!:\n'
)
else
local success, result = pcall(function()
oat._patchFunction(func, actor)(unpack(v[2]))
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
end
-- now that we know there's no poisonous methods in queue, let's offload them
for _, v in ipairs(queue) do
table.insert(M._globalQueue, {actor, v})
end
-- let's also properly route everything from the proxied actor to the actual actor
lockedActor = actor
-- and now let's run the initcommands
for _, c in ipairs(initCommands) do
local func = c[1]
local success, result = pcall(function()
func(actor)
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' InitCommand defined on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
-- to make mr. Garbage Collector's job easier
initCommands = {}
queueRepresentation = nil
queue = {}
end
else
return function(...)
if M._actorsInitialized then return end
if key == 'addcommand' and arg[2] == 'Init' then
table.insert(initCommands, {arg[3], debug.getinfo(2, 'Sl')})
else
table.insert(queue, {key, arg, debug.getinfo(2, 'Sl')})
end
end
end
end,
__newindex = function()
error('uranium: cannot set properties on actors!', 2)
end,
__tostring = function() return 'Proxy of ' .. name end,
__name = name
})
end
local function createGenericFunc(type)
return function()
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor(type)
table.insert(M._actorQueue, {
type = type,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
end
--- Defines a Quad actor.
---@type fun(): Quad
Quad = createGenericFunc('Quad')
--- Defines an ActorProxy actor.
---@type fun(): ActorProxy
ActorProxy = createGenericFunc('ActorProxy')
--- Defines a Polygon actor.
---@type fun(): Polygon
Polygon = createGenericFunc('Polygon')
--- Defines an ActorFrameTexture actor.
---@type fun(): ActorFrameTexture
ActorFrameTexture = createGenericFunc('ActorFrameTexture')
---@param file string | nil
---@return Sprite
--- Defines a Sprite actor.
function Sprite(file)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
--if not file then error('uranium: cannot create a Sprite without a file', 2) end
local actor = createProxyActor('Sprite')
local type = nil
if not file then type = 'Sprite' end
table.insert(M._actorQueue, {
type = type,
file = file and uranium.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@return ActorFrame
---@see addChild
--- Defines an ActorFrame. Add children to it with `addChild`.
function ActorFrame()
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('ActorFrame')
table.insert(M._actorQueue, {
type = 'ActorFrame',
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
oat._actorAssociationTable[actor] = {}
return actor
end
local function isShaderCode(str)
return string.find(str or '', '\n')
end
---@param frag string | nil
---@param vert string | nil
---@return RageShaderProgram
--- Defines a shader. `frag` and `vert` can either be filenames or shader code.
function Shader(frag, vert)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('RageShaderProgram')
local fragFile = frag
local vertFile = vert
local isFragShaderCode = isShaderCode(frag)
local isVertShaderCode = isShaderCode(vert)
if isFragShaderCode then fragFile = nil end
if isVertShaderCode then vertFile = nil end
if (frag and vert) and ((isFragShaderCode and not isVertShaderCode) or (not isFragShaderCode and isVertShaderCode)) then
error('uranium: cannot create a shader with 1 shader file and 1 shader code block', 2)
end
table.insert(M._actorQueue, {
type = 'Sprite',
frag = fragFile and ('../' .. fragFile) or 'nop.frag',
vert = vertFile and ('../' .. vertFile) or 'nop.vert',
init = function(a)
a:hidden(1)
actor.__lock(a:GetShader())
-- shader code stuff
if isFragShaderCode or isVertShaderCode then
a:GetShader():compile(vert or '', frag or '')
end
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@param file string
---@return RageTexture
--- Defines a texture.
function Texture(file)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create a texture without a file', 2) end
local actor = createProxyActor('RageTexture')
table.insert(M._actorQueue, {
file = file and uranium.dir .. file,
init = function(a)
a:hidden(1)
actor.__lock(a:GetTexture())
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@param file string
---@return Model
--- Defines a Model actor.
function Model(file)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create a Model without a file', 2) end
local actor = createProxyActor('Model')
table.insert(M._actorQueue, {
type = nil,
file = file and uranium.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@param font string?
---@param text string?
---@return BitmapText
--- Defines a BitmapText actor.
function BitmapText(font, text)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('BitmapText')
table.insert(M._actorQueue, {
type = 'BitmapText',
font = font or 'common',
init = function(a)
if text then a:settext(text) end
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@param file string
---@return ActorSound
--- Defines an ActorSound actor.
function ActorSound(file)
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
if not file then error('uranium: cannot create an ActorSound without a file', 2) end
local actor = createProxyActor('ActorSound')
table.insert(M._actorQueue, {
type = 'ActorSound',
file = uranium.dir .. file,
init = function(a)
actor.__lock(a)
end
})
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
return actor
end
---@param frame ActorFrame
---@param actor Actor
--- Adds a child to an ActorFrame. **Please be aware of the side-effects!**
function addChild(frame, actor)
if not frame or not actor then
error('uranium: frame and actor must both Exist', 2)
end
if M._actorsInitializing then
error('uranium: cannot create frame-child associations during actor initialization', 2)
end
if M._actorsInitialized then
error('uranium: cannot create frame-child associations after actors have been initialized', 2)
end
if not frame.__lock then
error('uranium: ActorFrame passed into addChild must be one instantiated with ActorFrame()!', 2)
end
if not actor.__lock then
error('uranium: trying to add a child to an ActorFrame that isn\'t an actor; please read the first half of \'ActorFrame\'', 2)
end
M._actorAssociationQueue[actor.__queue] = frame.__queue
table.insert(oat._actorAssociationTable[frame], actor)
end
function M._transformQueueToTree()
local tree = {}
local paths = {}
local iter = 0
while #M._actorQueue > 0 do
iter = iter + 1
if iter > 99999 then
error('uranium: failed to transform queue to tree: reached maximum iteration limit! is there an actor with an invalid actorframe?')
end
for i = #M._actorQueue, 1, -1 do
v = M._actorQueue[i]
local insertInto
if not M._actorAssociationQueue[v] then
insertInto = tree
else
if paths[M._actorAssociationQueue[v]] then
insertInto = paths[M._actorAssociationQueue[v]]
end
end
if insertInto then
if v.type == 'ActorFrame' then
table.insert(insertInto, {init = v.init})
table.remove(M._actorQueue, i)
paths[v] = insertInto[#insertInto]
else
table.insert(insertInto, v)
table.remove(M._actorQueue, i)
end
end
end
end
M._actorTree = tree
end
function M.prepareForActors()
M._actorsInitializing = true
M._transformQueueToTree()
--Trace(fullDump(M._actorTree))
M._currentPath = M._actorTree
end
function M.finalize()
oat._actor = nil
M._actorQueue = nil
M._actorAssociationQueue = nil
M._actorTree = nil
M._currentPath = nil
M._pastPaths = nil
M._currentActor = nil
end
return M

36
uranium/config.lua Normal file
View File

@ -0,0 +1,36 @@
-- Internal module for Uranium's configuration system, meant to be used
-- for other systems to access the values.
local M = {}
-- Uranium's configuration system, providing methods to configure parts
-- of the template.
uranium.config = {}
M.resetOnFrameStart = false
-- Toggle actor resetting on frame start behavior by default.
---@param bool boolean
function uranium.config.resetOnFrameStart(bool)
M.resetOnFrameStart = bool
end
---@type table<Actor, boolean>
M.resetActorOnFrameStart = {}
-- Toggle actor resetting on frame start for individual actors. `bool` defaults to the opposite of your `resetOnFrameStart` config
---@param actor Actor
---@param bool boolean | nil
function uranium.config.resetActorOnFrameStart(actor, bool)
if bool == nil then bool = not M.resetOnFrameStart end
M.resetActorOnFrameStart[actor.__raw or actor] = bool
end
M.hideThemeActors = true
-- Toggle if theme actors (lifebars, scores, song names, etc.) are hidden. Must be toggled **before** `init`.
---@param bool boolean
function uranium.config.hideThemeActors(bool)
M.hideThemeActors = bool
end
return M

33
uranium/constants.lua Normal file
View File

@ -0,0 +1,33 @@
-- indexing things on _G is slower than
-- having access to them in a local `oat` table
-- that already acts as _G, so we move commonly
-- use values over
local function copy(src)
local dest = {}
for k, v in pairs(src) do
dest[k] = v
end
return dest
end
oat = _G.oat
type = _G.type
print = _G.print
pairs = _G.pairs
ipairs = _G.ipairs
unpack = _G.unpack
tonumber = _G.tonumber
tostring = _G.tostring
math = copy(_G.math)
table = copy(_G.table)
string = copy(_G.string)
-- convinience shortcuts employed by most templates
scx = SCREEN_CENTER_X
scy = SCREEN_CENTER_Y
sw = SCREEN_WIDTH
sh = SCREEN_HEIGHT
dw = DISPLAY:GetDisplayWidth()
dh = DISPLAY:GetDisplayHeight()

51
uranium/events.lua Normal file
View File

@ -0,0 +1,51 @@
useProfiler = false
---@class ProfilerInfo
---@field public t number
---@field public src string
---@type table<string, ProfilerInfo>
profilerInfo = {}
local callbacks = {}
local debugCache = {}
---@param event string
---@param ... any
---@return any
--- Call a defined callback.
function uranium.call(event, ...)
if callbacks[event] then
profilerInfo[event] = {}
for _, callback in ipairs(callbacks[event]) do
local start = os.clock()
local res = callback(unpack(arg))
local dur = os.clock() - start
if useProfiler then
if not debugCache[callback] then
debugCache[callback] = debug.getinfo(callback, 'Sl') -- cached cus debug.getinfo is EXPENSIVE
end
local finfo = debugCache[callback]
table.insert(profilerInfo[event], {
src = finfo.short_src .. ':' .. finfo.linedefined,
t = dur
})
end
if res ~= nil then return res end
end
end
end
---@param event string
---@param f function
--- Register a callback handler.
function uranium.on(event, f)
if not callbacks[event] then
callbacks[event] = {}
end
table.insert(callbacks[event], f)
end

175
uranium/main.lua Normal file
View File

@ -0,0 +1,175 @@
require 'uranium.constants'
require 'uranium.events'
local actors = require 'uranium.actors'
local config = require 'uranium.config'
local hasExited = false
local function exit()
if hasExited then return end
hasExited = true
uranium.call('exit')
-- good templates clean up after themselves
uranium = nil
_G.oat = nil
---@diagnostic disable-next-line: assign-type-mismatch
oat = nil
_main:hidden(1)
collectgarbage()
end
function backToSongWheel(message)
if message then
SCREENMAN:SystemMessage(message)
print(message)
end
exit()
GAMESTATE:FinishSong()
-- disable update_command
_main:hidden(1)
end
local function onCommand(self)
actors._actorsInitialized = true
actors._actorsInitializing = false
local resetOnFrameStartActors_ = {}
for k,v in pairs(config.resetActorOnFrameStart) do
resetOnFrameStartActors_[k.__raw] = v
end
config.resetActorOnFrameStart = resetOnFrameStartActors_
uranium.call('init')
end
-- runs once during ScreenReadyCommand, before the user code is loaded
-- hides various actors that are placed by the theme
local function hideThemeActors()
for _, element in ipairs {
'Overlay', 'Underlay',
'ScoreP1', 'ScoreP2',
'LifeP1', 'LifeP2',
'PlayerOptionsP1', 'PlayerOptionsP2', 'SongOptions',
'LifeFrame', 'ScoreFrame',
'DifficultyP1', 'DifficultyP2',
'BPMDisplay',
'MemoryCardDisplayP1', 'MemoryCardDisplayP2'
} do
local child = SCREENMAN(element)
if child then child:hidden(1) end
end
end
GAMESTATE:ApplyModifiers('clearall')
local lastt = GAMESTATE:GetSongTime()
local function screenReadyCommand(self)
actors.finalize()
if config.hideThemeActors then
hideThemeActors()
end
self:hidden(0)
collectgarbage()
local errored = false
local firstrun = true
local playersLoaded = false
self:addcommand('Update', function()
if errored then
return 0
end
errored = true
local P1, P2 = SCREENMAN('PlayerP1'), SCREENMAN('PlayerP2')
if P1 and P2 then
playersLoaded = true
end
if playersLoaded and not P1 and not P2 then -- sora exit hack
exit()
end
t = os.clock()
b = GAMESTATE:GetSongBeat()
local dt = t - lastt
lastt = t
if firstrun then
firstrun = false
dt = 0
self:GetChildren()[2]:hidden(1)
uranium.call('ready')
end
drawfunctionArguments = {}
for _, q in ipairs(actors._globalQueue) do
local enabled = config.resetOnFrameStart
local actor = q[1]
local v = q[2]
local pref = config.resetActorOnFrameStart[actor]
if pref ~= nil then enabled = pref end
if enabled then
local func = actor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
oat._patchFunction(func, actor)(unpack(v[2]))
end
end
end
uranium.call('preUpdate', dt)
uranium.call('update', dt)
uranium.call('postUpdate', dt)
errored = false
return 0
end)
self:luaeffect('Update')
end
---@class UraniumRelease
---@field branch string
---@field commit string
---@field version string
---@field name string
---@field prettyName string
---@field homeURL string
---@type UraniumRelease
uranium.release = {}
if not pcall(function() uranium.release = require('uranium.release') end) then
uranium.release = require('uranium.release_blank')
end
local success, result = pcall(function()
return require('main')
end)
if success then
print('---')
actors.prepareForActors()
_main:addcommand('On', onCommand)
_main:addcommand('Ready', screenReadyCommand)
_main:addcommand('Off', exit)
_main:addcommand('SaltyReset', exit)
_main:addcommand('WindowFocus', function()
uranium.call('focus', true)
end)
_main:addcommand('WindowFocusLost', function()
uranium.call('focus', false)
end)
_main:queuecommand('Ready')
else
Trace('got an error loading main.lua!')
Trace(result)
backToSongWheel('loading .lua file failed, check log for details')
error('uranium: loading main.lua file failed:\n' .. result)
end