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)
_G.oat = {}
oat._main = self
oat.dir = GAMESTATE:GetCurrentSong():GetSongDir()
setmetatable(oat, {
-- if something isn't found in the table, fall back to a global lookup
@ -14,6 +13,9 @@
end
})
uranium = {}
uranium.dir = GAMESTATE:GetCurrentSong():GetSongDir()
-- make require work
-- stolen from mirin template
-- https://github.com/XeroOl/notitg-mirin/blob/0fbff2ee93d905feeb58c4aac4fe7f5f9ebc9647/template/std.lua#L17
@ -33,7 +35,7 @@
local filename = string.gsub(modname, '%.', '/')
for path in (string.gfind or string.gmatch)(oat.package.path, '[^;]+') do
-- get the file path
local filepath = oat.dir .. string.gsub(path, '%?', filename)
local filepath = uranium.dir .. string.gsub(path, '%?', filename)
-- check if file exists
if not GAMESTATE:GetFileStructure(filepath) then
table.insert(errors, 'no file \''..filepath..'\'')
@ -79,792 +81,10 @@
oat()
local function copy(src)
local dest = {}
for k, v in pairs(src) do
dest[k] = v
end
return dest
end
require 'uranium.main'
oat.oat = _G.oat
oat.type = _G.type
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
-- Needed by StepMania, in order to not kill lua mods early
self:sleep(9e9)
-- 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

View File

@ -17,4 +17,15 @@ function self.aft(self)
self:Create()
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
---@meta
-- cleaning up some notitg typing jank... ehe
---@alias int number
---@alias int integer
---@alias float number
---@alias Quad Actor
---@alias void nil
@ -16,82 +16,6 @@ PROFILEMAN = {}
---@type RageInput
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
--- A simple timer. Ticks upwards at a rate of 1/sec.
---
@ -127,26 +51,11 @@ dw = 0
--- The display height.
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?*
oat = _G
---@class ProfilerInfo
---@field public t number
---@field public src string
---@type table<string, ProfilerInfo>
profilerInfo = {}
--- The Uranium Template table! All template-related functionality is stored here.
uranium = {}
---@type string
--- A shorthand for `GAMESTATE:GetCurrentSong():GetSongDir()`.
uranium.dir = nil

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