mirror of
https://github.com/AquariaOSE/Aquaria.git
synced 2024-11-29 22:35:45 +00:00
3b759294df
This uses a new <Compatibility script="..." /> tag in a mod's XML file. A compatbility layer is a script that runs before mod-init.lua is loaded, and before any mod scripts are loaded when resuming a saved game. This is a better solution than shipping a fragile wrapper with every mod, that tends to break and then needs to be updated. Now this wrapper is centralized, easy to use, and easy to update. Closes #31.
100 lines
3.2 KiB
Lua
100 lines
3.2 KiB
Lua
-- Script loader hijack
|
|
-- Intercepts loads of script files and patches interface functions on the fly
|
|
|
|
assert(AQUARIA_VERSION)
|
|
|
|
local rawset = rawset
|
|
local rawget = rawget
|
|
local debug = rawget(_G, "debug")
|
|
local errorLog_o = assert(errorLog, "errorLog() not present")
|
|
|
|
-- IMPORTANT that this gets loaded only once. Exit & re-enter the mod if the file was changed.
|
|
if rawget(_G, ".__COMPAT_LOADED") then
|
|
errorLog_o("ERROR: Compat loader already loaded")
|
|
return
|
|
end
|
|
|
|
local function formatStack(lvl)
|
|
if debug then
|
|
return debug.traceback("", lvl or 1) or "[No traceback available]"
|
|
end
|
|
return "[No debug library available]"
|
|
end
|
|
|
|
local function errorLogWrap(s, level)
|
|
return errorLog_o(s .. "\n" .. formatStack(level or 2))
|
|
end
|
|
rawset(_G, "errorLog", errorLogWrap)
|
|
|
|
---------------------------------------------------------------------
|
|
---- Create hook scripts for entity interface function detouring ----
|
|
---------------------------------------------------------------------
|
|
|
|
local WARNINGS = isDeveloperKeys()
|
|
local HOOKS = dofile("mod-compat.lua") or {}
|
|
assert(type(HOOKS) == "table", "mod-compat.lua must return nothing or table, not " .. type(HOOKS))
|
|
|
|
local v_meta
|
|
if WARNINGS then
|
|
local function _warnUndefInstance(tab, key)
|
|
if WARNINGS then
|
|
errorLog("WARNING: script tried to get/call undefined instance variable " .. tostring(key), 3)
|
|
rawset(tab, key, false) -- warn only once, not spam
|
|
end
|
|
end
|
|
v_meta = { __index = _warnUndefInstance }
|
|
end
|
|
|
|
-- Callback function, to be called whenever a new script is loaded and stored by the scripting interface.
|
|
-- Note: This function must never raise an error, otherwise the program will crash!
|
|
local function onCreateScript(scriptname, functable)
|
|
debugLog("=== Creating script instance: " .. tostring(scriptname) .. " ===")
|
|
assert(type(functable) == "table")
|
|
|
|
-- detour init() function to patch v to produce reasonable stackdumps,
|
|
-- and optionally call custom hook
|
|
local oldinit = functable.init
|
|
local function newinit(me)
|
|
assert(v)
|
|
setmetatable(v, v_meta) -- global lookup: uses entity context's v
|
|
if oldinit then
|
|
oldinit(me)
|
|
end
|
|
if me and me ~= 0 then
|
|
local hook = HOOKS.init
|
|
if hook then
|
|
return hook(scriptname, me)
|
|
end
|
|
end
|
|
end
|
|
functable.init = newinit
|
|
|
|
local postInitHook = HOOKS.postInit
|
|
if postInitHook then
|
|
local oldpostinit = functable.postInit
|
|
function functable.postInit(me)
|
|
oldpostinit(me)
|
|
postInitHook(me)
|
|
end
|
|
end
|
|
|
|
local f = HOOKS.onCreateScript
|
|
if f then
|
|
f(scriptname, functable)
|
|
end
|
|
end
|
|
|
|
-- Hidden, secret global table that stores interface functions for script templates
|
|
local _scriptfuncs = assert(_scriptfuncs, "_scriptfuncs missing")
|
|
|
|
-- Intercept writes to _scriptfuncs. Game uses lua_setfield() to populate the table, which honors metatables.
|
|
setmetatable(_scriptfuncs, {
|
|
__newindex = function(tab, scriptname, functable)
|
|
onCreateScript(scriptname, functable)
|
|
-- do the set, or it will crash
|
|
rawset(tab, scriptname, functable)
|
|
end
|
|
})
|
|
|
|
rawset(_G, ".__COMPAT_LOADED", true)
|
|
debugLog("COMPAT/loader: Installed")
|