2022-07-23 15:36:21 +00:00
-- 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
2022-07-29 22:32:11 +00:00
-- Emulated print function that behaves more like the standard lua one.
2022-07-23 15:36:21 +00:00
function print ( ... )
2022-07-29 22:32:11 +00:00
local n = select ( " # " , ... )
2022-07-23 15:36:21 +00:00
2022-07-29 22:32:11 +00:00
--for i, v in ipairs(arg) do
local stringArgs = { }
for i = 1 , n do
table.insert ( stringArgs , tostring ( select ( i , ... ) ) )
2022-07-23 15:36:21 +00:00
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 { }
2022-08-29 14:21:01 +00:00
package.path = package.path or " ./?.lua " -- Allow paths relative to the working directory.
2022-07-23 15:36:21 +00:00
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,
}
2022-07-29 22:32:11 +00:00
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
2022-07-23 18:43:04 +00:00
local oldRequire = require
2022-07-23 19:15:34 +00:00
local recursionSet = { }
2022-07-23 15:36:21 +00:00
---Emulated require function in case the Lua API is restricted.
2022-08-27 12:32:01 +00:00
---It's probably good enough for most use cases.
2022-07-23 18:43:04 +00:00
---
2022-07-29 22:32:11 +00:00
---We need to override the default require in any case, as only dofile and loadfile can access stuff in the virtual filesystem.
2022-07-23 15:36:21 +00:00
---@param modName string
2022-07-29 22:32:11 +00:00
---@return any ...
2022-07-23 18:43:04 +00:00
function require ( modName )
2022-07-23 15:36:21 +00:00
-- Check if package was already loaded, return previous result.
2022-07-23 18:43:04 +00:00
if package.loaded [ modName ] ~= nil then return package.loaded [ modName ] end
2022-07-23 15:36:21 +00:00
2022-07-23 19:15:34 +00:00
if recursionSet [ modName ] then
2022-07-29 22:32:11 +00:00
recursionSet = { }
error ( string.format ( " Cyclic dependency with module %q " , modName ) )
2022-07-23 19:15:34 +00:00
end
2022-07-29 22:32:11 +00:00
recursionSet [ modName ] = true
2022-07-23 19:15:34 +00:00
2022-07-23 15:36:21 +00:00
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
2022-07-29 22:32:11 +00:00
recursionSet [ modName ] = nil
2022-07-23 15:36:21 +00:00
return res
else
2022-07-23 18:43:04 +00:00
notFoundStr = notFoundStr .. string.format ( " \t no field package.preload['%s'] \n " , modName )
2022-07-23 15:36:21 +00:00
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.
2022-07-29 22:32:11 +00:00
local func , err = loadfile ( fixedPath )
if func then
local state , res = pcall ( func )
if not state then
recursionSet = { }
error ( res )
end
2022-07-23 15:36:21 +00:00
if res == nil then res = true end
package.loaded [ modName ] = res
2022-07-29 22:32:11 +00:00
recursionSet [ modName ] = nil
2022-07-23 15:36:21 +00:00
return res
2022-07-29 22:32:11 +00:00
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 ( " \t no file '%s' \n " , filePath )
else
recursionSet = { }
error ( err )
2022-07-23 15:36:21 +00:00
end
else
2022-07-23 18:43:04 +00:00
notFoundStr = notFoundStr .. string.format ( " \t not 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
2022-07-29 22:32:11 +00:00
recursionSet [ modName ] = nil
2022-07-23 18:43:04 +00:00
return res
else
notFoundStr = notFoundStr .. string.format ( " \t original require:%s " , res )
2022-07-23 15:36:21 +00:00
end
end
2022-07-29 22:32:11 +00:00
recursionSet = { }
2022-07-23 15:36:21 +00:00
error ( string.format ( " module %q not found: \n %s " , modName , notFoundStr ) )
end
---Set up some stuff so `require` works as expected.
2022-07-23 18:43:04 +00:00
---@param libPath any -- Path to the libraries directory of this mod.
local function setup ( libPath )
2022-08-29 14:21:01 +00:00
-- Add the library directory of the mod as base for any `require` lookups.
package.path = package.path : gsub ( " ;$ " , " " ) -- Get rid of any trailing semicolon.
package.path = package.path .. " ;./ " .. libPath .. " ?.lua "
package.path = package.path .. " ;./ " .. libPath .. " ?/init.lua "
2022-07-23 18:43:04 +00:00
-- Add the library directory of Noita itself.
2022-08-29 14:21:01 +00:00
package.path = package.path .. " ;./data/scripts/lib/?.lua " -- TODO: Get rid of Noita's lib path, create replacement libs for stuff in there
2022-07-23 15:36:21 +00:00
end
return setup