uranium-core/main.xml

711 lines
19 KiB
XML

<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
__index = _G,
-- handle oat() calls to set the environment
__call = function(self, f)
setfenv(f or 2, self)
return f
end
})
-- make require work
-- stolen from mirin template
-- https://github.com/XeroOl/notitg-mirin/blob/0fbff2ee93d905feeb58c4aac4fe7f5f9ebc9647/template/std.lua#L17
oat.package = {
-- uranium template loader path
path = 'src/?.lua;src/?/init.lua;template/?.lua;template/?/init.lua',
preload = {},
loaded = {},
loaders = {
function(modname)
local preload = oat.package.preload[modname]
return preload or 'no field oat.package.preload[\''..modname..'\']'
end,
function(modname)
local errors = {}
-- get the filename
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)
-- check if file exists
if not GAMESTATE:GetFileStructure(filepath) then
table.insert(errors, 'no file \''..filepath..'\'')
else
local loader, err = loadfile(filepath)
-- check if file loads properly
if err then
error(err, 3)
elseif loader then
return oat(loader)
end
end
end
return table.concat(errors, '\n')
end,
},
}
function oat.require(modname)
local loaded = oat.package.loaded
if not loaded[modname] then
local errors = {'module \''..modname..'\' not found:'}
local chunk
for _, loader in ipairs(oat.package.loaders) do
local result = loader(modname)
if type(result) == 'string' then
table.insert(errors, result)
elseif type(result) == 'function' then
chunk = result
break
end
end
if not chunk then
error(table.concat(errors, '\n'), 2)
end
loaded[modname] = chunk()
if loaded[modname] == nil then
loaded[modname] = true
end
end
return loaded[modname]
end
oat()
local function copy(src)
local dest = {}
for k, v in pairs(src) do
dest[k] = v
end
return dest
end
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 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
_G.oat = nil
_main:hidden(1)
end
local actorsInitialized = false -- if true, no new actors can be created
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
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',
} do
local child = SCREENMAN(element)
if child then child:hidden(1) end
end
end
local lastt = GAMESTATE:GetSongTime()
local function screen_ready_command(self)
hideThemeActors()
self:hidden(0)
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
for _, q in ipairs(globalQueue) do
local actor = q[1]
local v = q[2]
local func = actor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
patchFunction(func, actor)(unpack(v[2]))
end
end
uranium:call('update', dt)
errored = false
return 0
end)
self:luaeffect('Update')
end
GAMESTATE:ApplyModifiers('clearall')
-- 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)
if newFrame and not (currentActor and forceActor) then
table.insert(pastPaths, currentPath)
currentPath = currentPath[idx]
table.remove(pastPaths[#pastPaths], idx)
return true
elseif currentActor 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(i)
if a.fov then -- actorframe
a:Draw()
end
end
end)
if currentPath.init then
currentPath.init(self)
currentPath.init = nil
end
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
local val = lockedActor[key]
if type(val) == 'function' then
return patchFunction(val, lockedActor)
end
return val
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
initCommands = {}
queueRepresentation = nil -- to make mr. Garbage Collector's job easier
end
else
return function(...)
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 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 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 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])
return actor
end
local function isShaderCode(str)
return string.find(str or '', '\n')
end
function Shader(frag, vert)
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 Model(file)
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 actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
local actor = createProxyActor('BitmapText')
table.insert(actorQueue, {
type = 'BitmapText',
font = font and (oat.dir .. 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 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)
actorAssociationQueue[actor.__queue] = frame.__queue
end
local function transformQueueToTree()
local tree = {}
local paths = {}
local iter = 0
while #actorQueue > 0 do
iter = iter + 1
if iter > 999999 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 success, result = pcall(function()
require('main')
require('stdlib.util')
end)
if success then
luaobj = result
print('---')
transformQueueToTree()
Trace(fullDump(actorTree))
currentPath = actorTree
nextActor()
self:addcommand('On', onCommand)
self:addcommand('Ready', screen_ready_command)
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.
-- By removing the command after it's done, it can only ever run once
self:removecommand('Init')
end"><children>
<Layer File="actors.xml" />
<Layer Type="Quad" InitCommand="xywh,SCREEN_CENTER_X,SCREEN_CENTER_Y,SCREEN_WIDTH,SCREEN_HEIGHT;diffuse,#000000;sleep,9e9"/>
</children></Layer>