mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-11-22 21:17:33 +00:00
David Vogel
403167b366
- Make dofile more conform to standard lua - Move dofile from live-reload.lua to compatibility.lua - Let dofile and require throw errors on script errors - Fix bug in recursion detection - Remove Noita's dofile annotation - Fix some EmmyLua annotations - Improve print replacement
153 lines
4.9 KiB
Lua
153 lines
4.9 KiB
Lua
-- Copyright (c) 2019-2022 David Vogel
|
|
--
|
|
-- This software is released under the MIT License.
|
|
-- https://opensource.org/licenses/MIT
|
|
|
|
-- Some code to make Noita's lua conform more to standard lua.
|
|
|
|
-- Stupid way to prevent this code from being called more than once per sandbox.
|
|
-- Calling this lua file with dofile_once would still cause the setup function to be called multiple times.
|
|
if _NoitaAPICompatibilityWrapperGuard_ then return function(dummy) end end
|
|
_NoitaAPICompatibilityWrapperGuard_ = true
|
|
|
|
local oldPrint = print
|
|
|
|
-- Emulated print function that behaves more like the standard lua one.
|
|
function print(...)
|
|
local n = select("#", ...)
|
|
|
|
--for i, v in ipairs(arg) do
|
|
local stringArgs = {}
|
|
for i = 1, n do
|
|
table.insert(stringArgs, tostring(select(i, ...)))
|
|
end
|
|
|
|
oldPrint(unpack(stringArgs))
|
|
end
|
|
|
|
-- Package doesn't exist when the Lua API is restricted.
|
|
-- Therefore we create it here and apply some default values.
|
|
package = package or {}
|
|
package.path = package.path or "./?.lua;" -- Allow paths relative to the working directory.
|
|
package.preload = package.preload or {}
|
|
package.loaded = package.loaded or {
|
|
_G = _G,
|
|
bit = bit,
|
|
coroutine = coroutine,
|
|
debug = debug,
|
|
math = math,
|
|
package = package,
|
|
string = string,
|
|
table = table,
|
|
--io = io,
|
|
--jit = jit,
|
|
--os = os,
|
|
}
|
|
|
|
local oldDofile = dofile
|
|
|
|
---Emulated dofile to execute a lua script from disk and circumvent any caching.
|
|
---Noita for some reason caches script files (Or loads them into its virtual filesystem)(Or caches compiled bytecode), so reloading script files from disk does not work without this.
|
|
---
|
|
---This conforms more with standard lua.
|
|
---@param path string
|
|
---@return any ...
|
|
function dofile(path)
|
|
local func, err = loadfile(path)
|
|
if not func then error(err) end
|
|
|
|
return func()
|
|
end
|
|
|
|
local oldRequire = require
|
|
|
|
local recursionSet = {}
|
|
|
|
---Emulated require function in case the Lua API is restricted.
|
|
---It's probably good enough for most usecases.
|
|
---
|
|
---We need to override the default require in any case, as only dofile and loadfile can access stuff in the virtual filesystem.
|
|
---@param modName string
|
|
---@return any ...
|
|
function require(modName)
|
|
-- Check if package was already loaded, return previous result.
|
|
if package.loaded[modName] ~= nil then return package.loaded[modName] end
|
|
|
|
if recursionSet[modName] then
|
|
recursionSet = {}
|
|
error(string.format("Cyclic dependency with module %q", modName))
|
|
end
|
|
recursionSet[modName] = true
|
|
|
|
local notFoundStr = ""
|
|
|
|
-- Check if there is an entry in the preload table.
|
|
local preloadFunc = package.preload[modName]
|
|
if preloadFunc then
|
|
local res = preloadFunc(modName)
|
|
|
|
if res == nil then res = true end
|
|
package.loaded[modName] = res
|
|
recursionSet[modName] = nil
|
|
return res
|
|
else
|
|
notFoundStr = notFoundStr .. string.format("\tno field package.preload['%s']\n", modName)
|
|
end
|
|
|
|
-- Load and execute scripts.
|
|
-- Iterate over all package.path entries.
|
|
for pathEntry in string.gmatch(package.path, "[^;]+") do
|
|
local modPath = string.gsub(modName, "%.", "/") -- Replace "." with file path delimiter.
|
|
local filePath = string.gsub(pathEntry, "?", modPath, 1) -- Insert modPath into "?" placeholder.
|
|
local fixedPath = string.gsub(filePath, "^%.[\\/]", "") -- Need to remove "./" or ".\" at the beginning, as Noita allows only "data" and "mods".
|
|
if fixedPath:sub(1, 4) == "data" or fixedPath:sub(1, 4) == "mods" then -- Ignore everything other than data and mod root path elements. It's not perfect, but this is just there to prevent console spam.
|
|
local func, err = loadfile(fixedPath)
|
|
if func then
|
|
local state, res = pcall(func)
|
|
if not state then
|
|
recursionSet = {}
|
|
error(res)
|
|
end
|
|
if res == nil then res = true end
|
|
package.loaded[modName] = res
|
|
recursionSet[modName] = nil
|
|
return res
|
|
elseif err and err:sub(1, 45) == "Error loading lua script - file doesn't exist" then -- I hate to do that.
|
|
notFoundStr = notFoundStr .. string.format("\tno file '%s'\n", filePath)
|
|
else
|
|
recursionSet = {}
|
|
error(err)
|
|
end
|
|
else
|
|
notFoundStr = notFoundStr .. string.format("\tnot allowed '%s'\n", filePath)
|
|
end
|
|
end
|
|
|
|
-- Fall back to the original require, if it exists.
|
|
if oldRequire then
|
|
local ok, res = pcall(oldRequire, modName)
|
|
if ok then
|
|
recursionSet[modName] = nil
|
|
return res
|
|
else
|
|
notFoundStr = notFoundStr .. string.format("\toriginal require:%s", res)
|
|
end
|
|
end
|
|
|
|
recursionSet = {}
|
|
error(string.format("module %q not found:\n%s", modName, notFoundStr))
|
|
end
|
|
|
|
---Set up some stuff so `require` works as expected.
|
|
---@param libPath any -- Path to the libraries directory of this mod.
|
|
local function setup(libPath)
|
|
-- Add the files folder of the given mod as base for any `require` lookups.
|
|
package.path = package.path .. "./" .. libPath .. "?.lua;"
|
|
package.path = package.path .. "./" .. libPath .. "?/init.lua;"
|
|
|
|
-- Add the library directory of Noita itself.
|
|
package.path = package.path .. "./data/scripts/lib/?.lua;" -- TODO: Get rid of Noita's lib path, create replacement libs for stuff in there
|
|
end
|
|
|
|
return setup
|