Refactoring, fixes and cleanup

- Move utils into Noita API wrapper
- Always overwrite require, with a fallback to the original
- Add library directory of mod to package.path, instead of the files directory
- Add Noita data/scripts/lib to package.path
- Fix dofile error handling
- Fix require not working right when module returns false
- Add init.lua to Noita API wrapper, that contains a table of all modules
- Fix Utils.GetSpecialDirectory
- Update README.md
This commit is contained in:
David Vogel 2022-07-23 20:43:04 +02:00
parent 0222350a7f
commit 931c4df18a
14 changed files with 143 additions and 76 deletions

View File

@ -3,19 +3,14 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
--------------------
-- Load libraries --
--------------------
--------------------------
-- Load library modules --
--------------------------
local JSON = require("libraries.noita-api.json")
local EntityAPI = require("libraries.noita-api.entity")
local Hilbert = require("libraries.hilbert-curve")
------------------
-- Load modules --
------------------
local Utils = require("utils")
local JSON = require("noita-api.json")
local EntityAPI = require("noita-api.entity")
local Utils = require("noita-api.utils")
local Hilbert = require("hilbert-curve")
----------
-- Code --

View File

@ -5,8 +5,16 @@
-- Viewport coordinates transformation (world <-> window) for Noita.
local Vec2 = require("libraries.noita-api.vec2")
local CameraAPI = require("libraries.noita-api.camera")
--------------------------
-- Load library modules --
--------------------------
local CameraAPI = require("noita-api.camera")
local Vec2 = require("noita-api.vec2")
----------
-- Code --
----------
---@class Coords
---@field InternalResolution Vec2

View File

@ -22,16 +22,17 @@ But this would be too complex, as there are a lot of edge cases and stuff that h
```lua
-- Emulate and override some functions and tables to make everything conform more to standard lua.
-- This will make `require` work, even in sandboxes with restricted Noita API.
local modFolder = "noita-mapcap"
dofile("mods/" .. modFolder .. "/files/libraries/noita-api/compatibility.lua")(modFolder)
local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath)
```
You need to set `modFolder` to your mod's directory name.
You need to adjust `libPath` to point into your mod's library directory.
The trailing `/` is needed!
After that you can import and use the library like this:
```lua
local EntityAPI = require("libraries.noita-api.entity")
local EntityAPI = require("noita-api.entity")
local x, y, radius = 10, 10, 100
@ -45,3 +46,9 @@ for _, entity in ipairs(entities) do
end
end
```
To include the whole set of API commands, use:
```lua
local NoitaAPI = require("noita-api")
```

View File

@ -3,7 +3,7 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local Vec2 = require("libraries.noita-api.vec2")
local Vec2 = require("noita-api.vec2")
-------------
-- Classes --

View File

@ -26,7 +26,7 @@ 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;"
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,
@ -42,13 +42,17 @@ package.loaded = package.loaded or {
--os = os,
}
local oldRequire = require
---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 can access stuff in the virtual filesystem.
---@param modName string
---@return any
local function customRequire(modName)
function require(modName)
-- Check if package was already loaded, return previous result.
if package.loaded[modName] then return package.loaded[modName] end
if package.loaded[modName] ~= nil then return package.loaded[modName] end
local notFoundStr = ""
@ -61,7 +65,7 @@ local function customRequire(modName)
package.loaded[modName] = res
return res
else
notFoundStr = notFoundStr .. string.format("\tno field package.preload[%q]\n", modName)
notFoundStr = notFoundStr .. string.format("\tno field package.preload['%s']\n", modName)
end
-- Load and execute scripts.
@ -72,29 +76,40 @@ local function customRequire(modName)
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 res, err = dofile(fixedPath)
if res == nil then
notFoundStr = notFoundStr .. string.format("\tno file %q\n", filePath)
if err then
notFoundStr = notFoundStr .. string.format("\tno file '%s'\n", filePath)
else
if res == nil then res = true end
package.loaded[modName] = res
return res
end
else
notFoundStr = notFoundStr .. string.format("\tnot allowed %q\n", filePath)
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
return res
else
notFoundStr = notFoundStr .. string.format("\toriginal require:%s", res)
end
end
error(string.format("module %q not found:\n%s", modName, notFoundStr))
end
require = require or customRequire
---Set up some stuff so `require` works as expected.
---@param modName any
local function setup(modName)
---@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 .. "./mods/" .. modName .. "/files/?.lua;"
--package.path = package.path .. "./mods/" .. modName .. "/files/?/init.lua;"
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;"
end
return setup

View File

@ -3,7 +3,7 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local JSON = require("libraries.noita-api.json")
local JSON = require("noita-api.json")
-------------
-- Classes --

View File

@ -3,7 +3,7 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local Vec2 = require("libraries.noita-api.vec2")
local Vec2 = require("noita-api.vec2")
-------------
-- Classes --

View File

@ -3,9 +3,8 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local Vec2 = require("libraries.noita-api.vec2")
local JSON = require("libraries.noita-api.json")
local ComponentAPI = require("libraries.noita-api.component")
local ComponentAPI = require("noita-api.component")
local JSON = require("noita-api.json")
-------------
-- Classes --

View File

@ -0,0 +1,14 @@
-- Copyright (c) 2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
return {
Camera = require("noita-api.camera"),
Component = require("noita-api.component"),
Debug = require("noita-api.debug"),
Entity = require("noita-api.entity"),
JSON = require("noita-api.json"),
Utils = require("noita-api.utils"),
Vec2 = require("noita-api.vec2"),
}

View File

@ -0,0 +1,54 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
-- This contains just some utilitites that may be useful to have.
local DebugAPI = require("noita-api.debug")
local Utils = {}
---Returns if the file at filePath exists.
---This only works correctly when API access is not restricted.
---@param filePath string
---@return boolean
function Utils.FileExists(filePath)
local f = io.open(filePath, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
local specialDirectoryDev = {
["save-shared"] = "save_shared/",
["save-stats"] = "save_stats/", -- Assumes that the first save is the currently used one.
["save"] = "save00/" -- Assumes that the first save is the currently used one.
}
local specialDirectory = {
["save-shared"] = "save_shared/",
["save-stats"] = "save00/stats/", -- Assumes that the first save is the currently used one.
["save"] = "save00/" -- Assumes that the first save is the currently used one.
}
---Returns the path to the special directory, or nil in case it couldn't be determined.
---This only works correctly when API access is not restricted.
---@param id "save-shared"|"save-stats"|"save"
---@return string|nil
function Utils.GetSpecialDirectory(id)
if DebugAPI.IsDevBuild() then
-- We are in the dev build.
return "./" .. specialDirectoryDev[id]
else
-- We are in the normal Noita executable.
-- Hacky way to get to LocalLow, there is just no other way to get this path. :/
local pathPrefix = os.getenv('APPDATA'):gsub("[^\\/]+$", "") .. "LocalLow/Nolla_Games_Noita/"
return pathPrefix .. specialDirectory[id]
end
end
return Utils

View File

@ -5,10 +5,10 @@
-- Emulate and override some functions and tables to make everything conform more to standard lua.
-- This will make `require` work, even in sandboxes with restricted Noita API.
local modFolder = "noita-mapcap"
dofile("mods/" .. modFolder .. "/files/libraries/noita-api/compatibility.lua")(modFolder)
local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath)
local EntityAPI = require("libraries.noita-api.entity")
local EntityAPI = require("noita-api.entity")
local oldPerkSpawn = perk_spawn

View File

@ -3,15 +3,11 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
--------------------
-- Load libraries --
--------------------
--------------------------
-- Load library modules --
--------------------------
------------------
-- Load modules --
------------------
local Utils = require("utils")
local Utils = require("noita-api.utils")
----------
-- Code --

View File

@ -1,25 +0,0 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local Utils = {}
---Returns if the file at filePath exists.
---@param filePath string
---@return boolean
function Utils.FileExists(filePath)
local f = io.open(filePath, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
function Utils.NoitaSpecialDirectory()
-- TODO: Implement NoitaSpecialDirectory function
end
return Utils

View File

@ -9,13 +9,17 @@
-- Emulate and override some functions and tables to make everything conform more to standard lua.
-- This will make `require` work, even in sandboxes with restricted Noita API.
local modFolder = "noita-mapcap"
dofile("mods/" .. modFolder .. "/files/libraries/noita-api/compatibility.lua")(modFolder)
local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath)
if not async then
dofile_once("data/scripts/lib/coroutines.lua")
require("coroutines") -- Loads Noita's coroutines library from `data/scripts/lib/coroutines.lua`.
end
-------------------------------
-- Load and run script files --
-------------------------------
dofile("mods/noita-mapcap/files/external.lua")
dofile("mods/noita-mapcap/files/capture.lua")
dofile("mods/noita-mapcap/files/ui.lua")