Improve user experience

- Modernise UI, and simplify its logic
- Add UI graphics
- Add modification.lua which contains everything to modify Noita settings
- Add message.lua which handles messages for users
- Add check.lua which checks things, triggers messages and suggest user actions
- Remove ACTIONS category from settings
- Add more live capturing parameters to settings
- Restrict vector input fields in settings
- Rename pixel-size setting to pixel-scale
- Let GetRect return two vectors instead of RECT object
- Add VirtualOffsetPixelPerfect and FullscreenMode field to Coords
- Fix captureScreenshot when the outputPixelScale is 0
- Show runtime errors in UI via message.lua
- Other small fixes
This commit is contained in:
David Vogel 2022-07-28 01:48:49 +02:00
parent f0217ba856
commit 31fc11ef1b
17 changed files with 609 additions and 172 deletions

3
.gitignore vendored
View File

@ -105,4 +105,5 @@ $RECYCLE.BIN/
/output/ /output/
/dist/ /dist/
/bin/stitch/output.png /bin/stitch/output.png
/files/magic-numbers/generated.xml

View File

@ -64,7 +64,12 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
---Top left in output coordinates. ---Top left in output coordinates.
---@type Vec2 ---@type Vec2
local outputTopLeft = (topLeftWorld * outputPixelScale):Rounded() local outputTopLeft
if outputPixelScale > 0 then
outputTopLeft = (topLeftWorld * outputPixelScale):Rounded()
else
outputTopLeft = topLeftWorld
end
-- Check if the file exists, and if we are allowed to overwrite it. -- Check if the file exists, and if we are allowed to overwrite it.
if dontOverwrite and Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", outputTopLeft.x, outputTopLeft.y)) then if dontOverwrite and Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", outputTopLeft.x, outputTopLeft.y)) then
@ -77,7 +82,7 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
repeat repeat
-- Prematurely stop capturing if that is requested by the context. -- Prematurely stop capturing if that is requested by the context.
if ctx and ctx:IsStopping() then return end if ctx and ctx:IsStopping() then return end
if delayFrames > 100 then if delayFrames > 100 then
-- Wiggle the screen a bit, as chunks sometimes don't want to load. -- Wiggle the screen a bit, as chunks sometimes don't want to load.
if pos then CameraAPI.SetPos(pos + Vec2(math.random(-100, 100), math.random(-100, 100))) end if pos then CameraAPI.SetPos(pos + Vec2(math.random(-100, 100), math.random(-100, 100))) end
@ -94,14 +99,18 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
end end
-- Suspend UI drawing for 1 frame. -- Suspend UI drawing for 1 frame.
UI.SuspendDrawing(1) UI:SuspendDrawing(1)
wait(0) wait(0)
-- Fetch coordinates again, as they may have changed. -- Fetch coordinates again, as they may have changed.
if not pos then if not pos then
topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos) topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = calculateCaptureRectangle(pos)
outputTopLeft = (topLeftWorld * outputPixelScale):Rounded() if outputPixelScale > 0 then
outputTopLeft = (topLeftWorld * outputPixelScale):Rounded()
else
outputTopLeft = topLeftWorld
end
end end
-- The top left world position needs to be upscaled by the pixel scale. -- The top left world position needs to be upscaled by the pixel scale.
@ -119,7 +128,7 @@ end
---@param scope "init"|"do"|"end" ---@param scope "init"|"do"|"end"
local function mapCapturingCtxErrHandler(err, scope) local function mapCapturingCtxErrHandler(err, scope)
print(string.format("Failed to capture map: %s", err)) print(string.format("Failed to capture map: %s", err))
-- TODO: Forward error to user interface Message:ShowRuntimeError("MapCaptureError", "Failed to capture map:", tostring(err))
end end
---Starts the capturing process in a spiral around origin. ---Starts the capturing process in a spiral around origin.
@ -128,6 +137,11 @@ end
---@param captureGridSize number -- The grid size in world pixels. ---@param captureGridSize number -- The grid size in world pixels.
---@param outputPixelScale number|nil -- The resulting image pixel to world pixel ratio. ---@param outputPixelScale number|nil -- The resulting image pixel to world pixel ratio.
function Capture:StartCapturingSpiral(origin, captureGridSize, outputPixelScale) function Capture:StartCapturingSpiral(origin, captureGridSize, outputPixelScale)
-- Create file that signals that there are files in the output directory.
local file = io.open("mods/noita-mapcap/output/nonempty", "a")
if file ~= nil then file:close() end
---Origin rounded to capture grid. ---Origin rounded to capture grid.
---@type Vec2 ---@type Vec2
local origin = (origin / captureGridSize):Rounded("Floor") * captureGridSize local origin = (origin / captureGridSize):Rounded("Floor") * captureGridSize
@ -180,6 +194,11 @@ end
---@param captureGridSize number -- The grid size in world pixels. ---@param captureGridSize number -- The grid size in world pixels.
---@param outputPixelScale number|nil -- The resulting image pixel to world pixel ratio. ---@param outputPixelScale number|nil -- The resulting image pixel to world pixel ratio.
function Capture:StartCapturingArea(topLeft, bottomRight, captureGridSize, outputPixelScale) function Capture:StartCapturingArea(topLeft, bottomRight, captureGridSize, outputPixelScale)
-- Create file that signals that there are files in the output directory.
local file = io.open("mods/noita-mapcap/output/nonempty", "a")
if file ~= nil then file:close() end
---The rectangle in grid coordinates. ---The rectangle in grid coordinates.
---@type Vec2, Vec2 ---@type Vec2, Vec2
local gridTopLeft, gridBottomRight = (topLeft / captureGridSize):Rounded("floor"), (bottomRight / captureGridSize):Rounded("floor") local gridTopLeft, gridBottomRight = (topLeft / captureGridSize):Rounded("floor"), (bottomRight / captureGridSize):Rounded("floor")
@ -241,6 +260,10 @@ function Capture:StartCapturingLive(interval, minDistance, maxDistance, outputPi
minDistance = minDistance or 10 minDistance = minDistance or 10
maxDistance = maxDistance or 50 maxDistance = maxDistance or 50
-- Create file that signals that there are files in the output directory.
local file = io.open("mods/noita-mapcap/output/nonempty", "a")
if file ~= nil then file:close() end
---Process main callback. ---Process main callback.
---@param ctx ProcessRunnerCtx ---@param ctx ProcessRunnerCtx
local function handleDo(ctx) local function handleDo(ctx)
@ -429,8 +452,31 @@ function Capture:StartCapturingEntities(store, modify)
---@param scope "init"|"do"|"end" ---@param scope "init"|"do"|"end"
local function handleErr(err, scope) local function handleErr(err, scope)
print(string.format("Failed to capture entities: %s", err)) print(string.format("Failed to capture entities: %s", err))
Message:ShowRuntimeError("EntitiesCaptureError", "Failed to capture entities:", tostring(err))
end end
-- Run process, if there is no other running right now. -- Run process, if there is no other running right now.
self.EntityCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr) self.EntityCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr)
end end
---Starts the capturing process based on user/mod settings.
function Capture:StartCapturing()
local mode = ModSettingGet("noita-mapcap.capture-mode")
local outputPixelScale = ModSettingGet("noita-mapcap.pixel-scale")
if mode == "live" then
local interval = ModSettingGet("noita-mapcap.live-interval")
local minDistance = ModSettingGet("noita-mapcap.live-min-distance")
local maxDistance = ModSettingGet("noita-mapcap.live-max-distance")
self:StartCapturingLive(interval, minDistance, maxDistance, outputPixelScale)
else
Message:ShowRuntimeError("StartCapturing", string.format("Unknown capturing mode %q", tostring(mode)))
end
end
---Stops all capturing processes.
function Capture:StopCapturing()
self.EntityCapturingCtx:Stop()
self.MapCapturingCtx:Stop()
end

87
files/check.lua Normal file
View File

@ -0,0 +1,87 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
-- Check if everything is alright.
-- This does mainly trigger user messages and suggest actions.
-----------------------
-- Load global stuff --
-----------------------
--------------------------
-- Load library modules --
--------------------------
local Coords = require("coordinates")
local ScreenCap = require("screen-capture")
local Vec2 = require("noita-api.vec2")
local Utils= require("noita-api.utils")
----------
-- Code --
----------
---Runs a list of checks at addon startup.
function Check:Startup()
if Utils.FileExists("mods/noita-mapcap/output/nonempty") then
Message:ShowOutputNonEmpty()
end
if not Utils.FileExists("mods/noita-mapcap/bin/capture-b/capture.dll") then
Message:ShowGeneralInstallationProblem("`capture.dll` is missing.", "Make sure you have installed the mod correctly.")
end
if not Utils.FileExists("mods/noita-mapcap/bin/stitch/stitch.exe") then
Message:ShowGeneralInstallationProblem("`stitch.exe` is missing.", "Make sure you have installed the mod correctly.", " ", "You can still use the mod to capture, though.")
end
end
---Runs a list of checks for everything resolution related.
---@param interval integer -- Check interval in frames.
function Check:Resolutions(interval)
interval = interval or 60
self.Counter = (self.Counter or 0) - 1
if self.Counter > 0 then return end
self.Counter = interval
-- Compare Noita config and actual window resolution.
local topLeft, bottomRight = ScreenCap.GetRect() -- Actual window client area.
if topLeft and bottomRight then
local actual = bottomRight - topLeft
if actual ~= Coords.WindowResolution then
Message:ShowWrongResolution(Modification.AutoSet, string.format("Old window resolution is %s. Current resolution is %s.", Coords.WindowResolution, actual))
end
else
Message:ShowRuntimeError("GetRect", "Couldn't determine window resolution.")
end
-- Check if we have the required settings.
local config, magic = Modification.RequiredChanges()
if config["fullscreen"] then
local expected = tonumber(config["fullscreen"])
if expected ~= Coords.FullscreenMode then
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Expected fullscreen mode %s. But got %s.", expected, Coords.FullscreenMode))
end
end
if config["window_w"] and config["window_h"] then
local expected = Vec2(tonumber(config["window_w"]), tonumber(config["window_h"]))
if expected ~= Coords.WindowResolution then
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Expected window resolution is %s. But got %s.", expected, Coords.WindowResolution))
end
end
if config["internal_size_w"] and config["internal_size_h"] then
local expected = Vec2(tonumber(config["internal_size_w"]), tonumber(config["internal_size_h"]))
if expected ~= Coords.InternalResolution then
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Expected internal resolution is %s. But got %s.", expected, Coords.InternalResolution))
end
end
if magic["VIRTUAL_RESOLUTION_X"] and magic["VIRTUAL_RESOLUTION_Y"] then
local expected = Vec2(tonumber(magic["VIRTUAL_RESOLUTION_X"]), tonumber(magic["VIRTUAL_RESOLUTION_Y"]))
if expected ~= Coords.VirtualResolution then
Message:ShowSetNoitaSettings(Modification.AutoSet, string.format("Expected virtual resolution is %s. But got %s.", expected, Coords.VirtualResolution))
end
end
end

View File

@ -34,18 +34,20 @@ local Vec2 = require("noita-api.vec2")
-- Code -- -- Code --
---------- ----------
local virtualOffsetPixelPerfect = Vec2(-2, 0)
---@class Coords ---@class Coords
---@field InternalResolution Vec2 -- Size of the internal rectangle in window pixels. ---@field InternalResolution Vec2 -- Size of the internal rectangle in window pixels.
---@field WindowResolution Vec2 -- Size of the window client area in window pixels. ---@field WindowResolution Vec2 -- Size of the window client area in window pixels.
---@field VirtualResolution Vec2 -- Size of the virtual rectangle in world/virtual pixels. ---@field VirtualResolution Vec2 -- Size of the virtual rectangle in world/virtual pixels.
---@field VirtualOffset Vec2 -- Offset of the virtual rectangle in world/virtual pixels. ---@field VirtualOffset Vec2 -- Offset of the virtual rectangle in world/virtual pixels.
---@field VirtualOffsetPixelPerfect Vec2 -- Offset of the virtual rectangle that maps chunks perfectly to the window.
---@field FullscreenMode integer -- The fullscreen mode the game is in. 0 is windowed.
local Coords = { local Coords = {
InternalResolution = Vec2(0, 0), InternalResolution = Vec2(0, 0),
WindowResolution = Vec2(0, 0), WindowResolution = Vec2(0, 0),
VirtualResolution = Vec2(0, 0), VirtualResolution = Vec2(0, 0),
VirtualOffset = Vec2(0, 0), VirtualOffset = Vec2(0, 0),
VirtualOffsetPixelPerfect = Vec2(-2, 0),
FullscreenMode = 0,
} }
---Reads and updates the internal, window and virtual resolutions from Noita's config files and API. ---Reads and updates the internal, window and virtual resolutions from Noita's config files and API.
@ -62,6 +64,7 @@ function Coords:ReadResolutions()
self.InternalResolution = Vec2(tonumber(xml.attr["internal_size_w"]), tonumber(xml.attr["internal_size_h"])) self.InternalResolution = Vec2(tonumber(xml.attr["internal_size_w"]), tonumber(xml.attr["internal_size_h"]))
self.VirtualResolution = Vec2(tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))) self.VirtualResolution = Vec2(tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y")))
self.VirtualOffset = Vec2(tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_Y"))) self.VirtualOffset = Vec2(tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_Y")))
self.FullscreenMode = tonumber(xml.attr["fullscreen"]) or 0
f:close() f:close()
return nil return nil
@ -130,7 +133,7 @@ function Coords:ToWindow(world, viewportCenter)
local internalTopLeft, internalBottomRight = self:InternalRect() local internalTopLeft, internalBottomRight = self:InternalRect()
local pixelScale = self:PixelScale() local pixelScale = self:PixelScale()
return internalTopLeft + (self.VirtualResolution / 2 + world - viewportCenter - virtualOffsetPixelPerfect + self.VirtualOffset) * pixelScale return internalTopLeft + (self.VirtualResolution / 2 + world - viewportCenter - self.VirtualOffsetPixelPerfect + self.VirtualOffset) * pixelScale
end end
---Converts the given window coordinates into world/virtual coordinates. ---Converts the given window coordinates into world/virtual coordinates.
@ -143,7 +146,7 @@ function Coords:ToWorld(window, viewportCenter)
local internalTopLeft, internalBottomRight = self:InternalRect() local internalTopLeft, internalBottomRight = self:InternalRect()
local pixelScale = self:PixelScale() local pixelScale = self:PixelScale()
return viewportCenter - self.VirtualResolution / 2 + (window - internalTopLeft) / pixelScale + virtualOffsetPixelPerfect - self.VirtualOffset return viewportCenter - self.VirtualResolution / 2 + (window - internalTopLeft) / pixelScale + self.VirtualOffsetPixelPerfect - self.VirtualOffset
end end
------------- -------------

View File

@ -42,14 +42,15 @@ function ScreenCap.Capture(topLeft, bottomRight, topLeftOutput, finalDimensions)
end end
---Returns the client rectangle of the "Main" window of this process in screen coordinates. ---Returns the client rectangle of the "Main" window of this process in screen coordinates.
---@return any ---@return Vec2|nil topLeft
---@return Vec2|nil bottomRight
function ScreenCap.GetRect() function ScreenCap.GetRect()
local rect = ffi.new("RECT") local rect = ffi.new("RECT")
if not res.GetRect(rect) then if not res.GetRect(rect) then
return nil return nil, nil
end end
return rect return Vec2(rect.left, rect.top), Vec2(rect.right, rect.bottom)
end end
return ScreenCap return ScreenCap

133
files/message.lua Normal file
View File

@ -0,0 +1,133 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
-----------------------
-- Load global stuff --
-----------------------
--------------------------
-- Load library modules --
--------------------------
local Coords = require("coordinates")
----------
-- Code --
----------
---Add a general runtime error message to the message list.
---This will always overwrite the last runtime error with the same id.
---@param id string
---@param ... string
function Message:ShowRuntimeError(id, ...)
self.List = self.List or {}
self.List["RuntimeError" .. id] = {
Type = "error",
Lines = { ... },
}
end
---Calls func and catches any exception.
---If there is one, a runtime error message will be shown to the user.
---@param id string
---@param func function
function Message:CatchException(id, func)
local ok, err = pcall(func)
if not ok then
self:ShowRuntimeError(id, string.format("An exception happened in %s", id), err)
end
end
---Request the user to let the addon automatically reset some Noita settings.
function Message:ShowResetNoitaSettings()
self.List = self.List or {}
self.List["ResetNoitaSettings"] = {
Type = "info",
Lines = {
"You requested to reset some game settings like:",
"- Custom resolutions",
" ",
"Press the following button to reset the settings and close Noita automatically:",
},
Actions = {
{ Name = "Reset and close", Hint = nil, HintDesc = nil, Callback = function() Modification:Reset() end },
},
}
end
---Request the user to let the addon automatically set Noita settings based on the given callback.
---@param callback function
---@param desc string -- What's wrong.
function Message:ShowSetNoitaSettings(callback, desc)
self.List = self.List or {}
self.List["SetNoitaSettings"] = {
Type = "warning",
Lines = {
"It seems that not all requested settings are applied to Noita:",
desc or "",
" ",
"Press the button at the bottom to set up and close Noita automatically.",
"Alternatively disable `Use custom resolution` in the mod settings.",
" ",
"You can always reset these settings by right clicking the `start capture`",
"button at the top left.",
},
Actions = {
{ Name = "Setup and close", Hint = nil, HintDesc = nil, Callback = callback },
},
}
end
---Request the user to let the addon automatically set Noita settings based on the given callback.
---@param callback function
---@param desc string -- What's wrong.
function Message:ShowWrongResolution(callback, desc)
self.List = self.List or {}
self.List["WrongResolution"] = {
Type = "warning",
Lines = {
"The resolution changed:",
desc or "",
" ",
"To fix: Restart Noita or revert the change."
},
Actions = {
{ Name = "Query settings again", Hint = nil, HintDesc = nil, Callback = function() Coords:ReadResolutions() end },
},
}
end
---Tell the user that there are files in the output directory.
function Message:ShowOutputNonEmpty()
self.List = self.List or {}
self.List["OutputNonEmpty"] = {
Type = "hint",
Lines = {
"There are already files in the output directory.",
"If you are continuing a capture session, ignore this message.",
" ",
"If you are about to capture a new map, make sure to delete all files in the output directory first."
},
Actions = {
{ Name = "Open output directory", Hint = nil, HintDesc = nil, Callback = function() os.execute("start .\\mods\\noita-mapcap\\output\\") end },
},
}
end
---Tell the user that there is something wrong with the mod installation.
---@param ... string
function Message:ShowGeneralInstallationProblem(...)
self.List = self.List or {}
self.List["GeneralInstallationProblem"] = {
Type = "error",
Lines = { ... },
}
end

120
files/modification.lua Normal file
View File

@ -0,0 +1,120 @@
-- Copyright (c) 2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
-- Noita settings/configuration modifications.
-- We try to keep modifications to a minimum, but some things have to be changed in order for the mod to work correctly.
--------------------------
-- Load library modules --
--------------------------
local NXML = require("luanxml.nxml")
local Utils = require("noita-api.utils")
local Vec2 = require("noita-api.vec2")
local Coords = require("coordinates")
----------
-- Code --
----------
---Will update Noita's `config.xml` with the values in the given table.
---
---This will force close Noita!
---@param config table<string, string> -- List of `config.xml` attributes that should be changed.
function Modification.SetConfig(config)
local configFilename = Utils.GetSpecialDirectory("save-shared") .. "config.xml"
-- Read and modify config.
local f, err = io.open(configFilename, "r")
if not f then error(string.format("failed to read config file: %s", err)) end
local xml = NXML.parse(f:read("*a"))
for k, v in pairs(config) do
xml.attr[k] = v
end
f:close()
-- Write modified config back.
local f, err = io.open(configFilename, "w")
if not f then error(string.format("failed to create config file: %s", err)) end
f:write(tostring(xml))
f:close()
-- We need to force close Noita, so it doesn't have any chance to overwrite the file.
os.exit(0)
end
---Will update Noita's `magic_numbers.xml` with the values in the given table.
---
---Should be called on mod initialization only.
---@param magic table<string, string> -- List of `magic_numbers.xml` attributes that should be changed.
function Modification.SetMagicNumbers(magic)
local xml = NXML.new_element("MagicNumbers", magic)
-- Write magic number file.
local f, err = io.open("mods/noita-mapcap/files/magic-numbers/generated.xml", "w")
if not f then error(string.format("failed to create config file: %s", err)) end
f:write(tostring(xml))
f:close()
ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/generated.xml")
end
---Returns tables with user requested game configuration changes.
---@return table config -- List of `config.xml` attributes that should be changed.
---@return table magic -- List of `magic_number.xml` attributes that should be changed.
function Modification.RequiredChanges()
local config, magic = {}, {}
-- Does the user request a custom resolution?
local customResolution = (ModSettingGet("noita-mapcap.custom-resolution-live") and ModSettingGet("noita-mapcap.capture-mode") == "live")
or (ModSettingGet("noita-mapcap.custom-resolution-other") and ModSettingGet("noita-mapcap.capture-mode") ~= "live")
if customResolution then
config["window_w"] = tostring(Vec2(ModSettingGet("noita-mapcap.window-resolution")).x)
config["window_h"] = tostring(Vec2(ModSettingGet("noita-mapcap.window-resolution")).y)
config["internal_size_w"] = tostring(Vec2(ModSettingGet("noita-mapcap.internal-resolution")).x)
config["internal_size_h"] = tostring(Vec2(ModSettingGet("noita-mapcap.internal-resolution")).y)
config["backbuffer_width"] = config["window_w"]
config["backbuffer_height"] = config["window_h"]
magic["VIRTUAL_RESOLUTION_X"] = tostring(Vec2(ModSettingGet("noita-mapcap.virtual-resolution")).x)
magic["VIRTUAL_RESOLUTION_Y"] = tostring(Vec2(ModSettingGet("noita-mapcap.virtual-resolution")).y)
end
-- Set virtual offset to be pixel perfect.
--magic["VIRTUAL_RESOLUTION_OFFSET_X"] = tostring(Coords.VirtualOffsetPixelPerfect.x)
--magic["VIRTUAL_RESOLUTION_OFFSET_Y"] = tostring(Coords.VirtualOffsetPixelPerfect.y)
-- Always expect a fullscreen mode of 0 (windowed).
-- Capturing will not work in fullscreen.
config["fullscreen"] = "0"
return config, magic
end
---Will change the game settings according to `Modification.RequiredChanges()`.
---
---This will force close Noita!
function Modification.AutoSet()
local config, magic = Modification.RequiredChanges()
Modification.SetConfig(config)
end
---Will reset all settings that may have been changed by this mod.
---
---This will force close Noita!
function Modification.Reset()
local config = {
window_w = "1280",
window_h = "720",
internal_size_w = "1280",
internal_size_h = "720",
backbuffer_width = "1280",
backbuffer_height = "720",
}
Modification.SetConfig(config)
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

BIN
files/ui-gfx/hint-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

BIN
files/ui-gfx/stop-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

View File

@ -3,17 +3,120 @@
-- This software is released under the MIT License. -- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT -- https://opensource.org/licenses/MIT
-----------------------
-- Load global stuff --
-----------------------
-- TODO: Wrap Noita utilities and wrap them into a table: https://stackoverflow.com/questions/9540732/loadfile-without-polluting-global-environment
require("utilities") -- Loads Noita's utilities from `data/scripts/lib/utilitites.lua`.
-------------------------- --------------------------
-- Load library modules -- -- Load library modules --
-------------------------- --------------------------
local Utils = require("noita-api.utils")
local ScreenCap = require("screen-capture")
---------- ----------
-- Code -- -- Code --
---------- ----------
---Returns unique IDs for the widgets.
---`_ResetID` has to be called every time before the UI is rebuilt.
---@return integer
function UI:_GenID()
self.CurrentID = (self.CurrentID or 0) + 1
return self.CurrentID
end
function UI:_ResetID()
self.CurrentID = nil
end
function UI:_DrawToolbar()
local gui = self.gui
GuiZSet(gui, 0)
GuiLayoutBeginHorizontal(gui, 2, 2, true, 2, 2)
if Capture.MapCapturingCtx:IsRunning() then
local clicked, clickedRight = GuiImageButton(gui, self:_GenID(), 0, 0, "", "mods/noita-mapcap/files/ui-gfx/stop-16x16.png")
GuiTooltip(gui, "Stop capture", "Stop the capturing process.\n \nRight click: Reset any modifications that this mod has done to Noita.")
if clicked then Capture:StopCapturing() end
if clickedRight then Message:ShowResetNoitaSettings() end
else
local clicked, clickedRight = GuiImageButton(gui, self:_GenID(), 0, 0, "", "mods/noita-mapcap/files/ui-gfx/record-16x16.png")
GuiTooltip(gui, "Start capture", "Start the capturing process based on mod settings.\n \nRight click: Reset any modifications that this mod has done to Noita.")
if clicked then Capture:StartCapturing() end
if clickedRight then Message:ShowResetNoitaSettings() end
end
local clicked = GuiImageButton(gui, self:_GenID(), 0, 0, "", "mods/noita-mapcap/files/ui-gfx/open-output-16x16.png")
GuiTooltip(gui, "Open output directory", "Reveals the output directory in your file browser.")
if clicked then os.execute("start .\\mods\\noita-mapcap\\output\\") end
GuiLayoutEnd(gui)
end
function UI:_DrawMessages(messages)
local gui = self.gui
-- Abort if there is no messages list.
if not messages then return end
GuiZSet(gui, 0)
-- Unfortunately you can't stack multiple layout containers with the same direction.
-- So keep track of the y position manually.
local posY = 60
for key, message in pairs(messages) do
GuiZSet(gui, -10)
GuiBeginAutoBox(gui)
GuiLayoutBeginHorizontal(gui, 27, posY, true, 5, 0) posY = posY + 20
if message.Type == "warning" or message.Type == "error" then
GuiImage(gui, self:_GenID(), 0, 0, "mods/noita-mapcap/files/ui-gfx/warning-16x16.png", 1, 1, 0, 0, 0, "")
elseif message.Type == "hint" or message.Type == "info" then
GuiImage(gui, self:_GenID(), 0, 0, "mods/noita-mapcap/files/ui-gfx/hint-16x16.png", 1, 1, 0, 0, 0, "")
else
GuiImage(gui, self:_GenID(), 0, 0, "mods/noita-mapcap/files/ui-gfx/hint-16x16.png", 1, 1, 0, 0, 0, "")
end
GuiLayoutBeginVertical(gui, 0, 0, false, 0, 0)
if type(message.Lines) == "table" then
for _, line in ipairs(message.Lines) do
GuiText(gui, 0, 0, tostring(line)) posY = posY + 11
end
end
if type(message.Actions) == "table" then
posY = posY + 11
for _, action in ipairs(message.Actions) do
local clicked = GuiButton(gui, self:_GenID(), 0, 11, ">" .. action.Name .. " <") posY = posY + 11
if action.Hint or action.HintDesc then
GuiTooltip(gui, action.Hint or "", action.HintDesc or "")
end
if clicked then
local ok, err = pcall(action.Callback)
if not ok then
Message:ShowRuntimeError("MessageAction", "Message action error:", err)
end
messages[key] = nil
end
end
end
GuiLayoutEnd(gui)
local clicked = GuiImageButton(gui, self:_GenID(), 5, 0, "", "mods/noita-mapcap/files/ui-gfx/dismiss-8x8.png")
--GuiTooltip(gui, "Dismiss message", "")
if clicked then messages[key] = nil end
GuiLayoutEnd(gui)
GuiZSet(gui, -9)
GuiEndAutoBoxNinePiece(gui, 5, 0, 0, false, 0, "data/ui_gfx/decorations/9piece0_gray.png", "data/ui_gfx/decorations/9piece0_gray.png")
end
end
---Stops the UI from drawing for the next few frames.
---@param frames integer
function UI:SuspendDrawing(frames) function UI:SuspendDrawing(frames)
self.suspendFrames = math.max(self.suspendFrames or 0, frames) self.suspendFrames = math.max(self.suspendFrames or 0, frames)
end end
@ -26,131 +129,15 @@ function UI:Draw()
if self.suspendFrames and self.suspendFrames > 0 then self.suspendFrames = self.suspendFrames - 1 return end if self.suspendFrames and self.suspendFrames > 0 then self.suspendFrames = self.suspendFrames - 1 return end
self.suspendFrames = nil self.suspendFrames = nil
-- Reset ID generator.
self:_ResetID()
GuiStartFrame(gui) GuiStartFrame(gui)
GuiLayoutBeginVertical(gui, 50, 20, false, 0, 0) GuiIdPushString(gui, "noita-mapcap")
GuiTextCentered(gui, 0, 0, "Heyho") self:_DrawToolbar()
self:_DrawMessages(Message.List)
GuiLayoutEnd(gui) GuiIdPop(gui)
if true then return end
GuiStartFrame(modGUI)
GuiLayoutBeginVertical(modGUI, 50, 20)
if not UiProgress then
-- Show informations
local problem
local rect = ScreenCap.GetRect()
if not rect then
GuiTextCentered(modGUI, 0, 0, '!!! WARNING !!! You are not using "Windowed" mode.')
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
GuiTextCentered(modGUI, 0, 0, '- Change the window mode in the game options to "Windowed"')
GuiTextCentered(modGUI, 0, 0, " ")
problem = true
end
if rect then
local screenWidth, screenHeight = rect.right - rect.left, rect.bottom - rect.top
local virtualWidth, virtualHeight = tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local ratioX, ratioY = screenWidth / virtualWidth, screenHeight / virtualHeight
--GuiTextCentered(modGUI, 0, 0, string.format("SCREEN_RESOLUTION_*: %d, %d", screenWidth, screenHeight))
--GuiTextCentered(modGUI, 0, 0, string.format("VIRTUAL_RESOLUTION_*: %d, %d", virtualWidth, virtualHeight))
if math.abs(ratioX - CAPTURE_PIXEL_SIZE) > 0.0001 or math.abs(ratioY - CAPTURE_PIXEL_SIZE) > 0.0001 then
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Screen and virtual resolution differ.")
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
GuiTextCentered(modGUI, 0, 0, string.format(
"- Change the resolution in the game options to %dx%d",
virtualWidth * CAPTURE_PIXEL_SIZE,
virtualHeight * CAPTURE_PIXEL_SIZE
))
GuiTextCentered(modGUI, 0, 0, string.format(
"- Change the virtual resolution in the mod to %dx%d",
screenWidth / CAPTURE_PIXEL_SIZE,
screenHeight / CAPTURE_PIXEL_SIZE
))
if math.abs(ratioX - ratioY) < 0.0001 then
GuiTextCentered(modGUI, 0, 0, string.format("- Change the CAPTURE_PIXEL_SIZE in the mod to %f", ratioX))
end
GuiTextCentered(modGUI, 0, 0, '- Make sure that the console is not selected')
GuiTextCentered(modGUI, 0, 0, " ")
problem = true
end
end
if not Utils.FileExists("mods/noita-mapcap/bin/capture-b/capture.dll") then
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Can't find library for screenshots.")
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
GuiTextCentered(modGUI, 0, 0, "- Redownload a release of this mod from GitHub, don't download the sourcecode")
GuiTextCentered(modGUI, 0, 0, " ")
problem = true
end
if not Utils.FileExists("mods/noita-mapcap/bin/stitch/stitch.exe") then
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Can't find software for stitching.")
GuiTextCentered(modGUI, 0, 0, "You can still take screenshots, but you won't be able to stitch those screenshots.")
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
GuiTextCentered(modGUI, 0, 0, "- Redownload a release of this mod from GitHub, don't download the sourcecode")
GuiTextCentered(modGUI, 0, 0, " ")
problem = true
end
if not problem then
GuiTextCentered(modGUI, 0, 0, "No problems found.")
GuiTextCentered(modGUI, 0, 0, " ")
end
GuiTextCentered(modGUI, 0, 0, "You can freely look around and search a place to start capturing.")
GuiTextCentered(modGUI, 0, 0, "When started the mod will take pictures automatically.")
GuiTextCentered(modGUI, 0, 0, "Use ESC to pause, and close the game to stop the process.")
GuiTextCentered(modGUI, 0, 0, 'You can resume capturing just by restarting noita and pressing "Start capturing map" again,')
GuiTextCentered(modGUI, 0, 0, "the mod will skip already captured files.")
GuiTextCentered(modGUI, 0, 0, 'If you want to start a new map, you have to delete all images from the "output" folder!')
--GuiTextCentered(modGUI, 0, 0, " ")
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_X"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_X"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_Y"))
GuiTextCentered(modGUI, 0, 0, " ")
if GuiButton(modGUI, 0, 0, ">> Start capturing map around view <<", 1) then
UiProgress = {}
startCapturingSpiral()
end
GuiTextCentered(modGUI, 0, 0, " ")
if GuiButton(modGUI, 0, 0, ">> Start capturing base layout <<", 1) then
UiProgress = {}
startCapturingHilbert(CAPTURE_AREA_BASE_LAYOUT)
end
if GuiButton(modGUI, 0, 0, ">> Start capturing main world <<", 1) then
UiProgress = {}
startCapturingHilbert(CAPTURE_AREA_MAIN_WORLD)
end
if GuiButton(modGUI, 0, 0, ">> Start capturing extended map <<", 1) then
UiProgress = {}
startCapturingHilbert(CAPTURE_AREA_EXTENDED)
end
if GuiButton(modGUI, 0, 0, ">> Start capturing run live <<", 1) then
UiProgress = {}
StartCapturingLive()
end
GuiTextCentered(modGUI, 0, 0, " ")
elseif not UiProgress.Done then
-- Show progress
local x, y = GameGetCameraPos()
GuiTextCentered(modGUI, 0, 0, string.format("Coordinates: %d, %d", x, y))
GuiTextCentered(modGUI, 0, 0, string.format("Waiting %d frames...", UiCaptureDelay))
if UiProgress.Progress then
GuiTextCentered(modGUI, 0, 0, progressBarString(
UiProgress, { BarLength = 100, CharFull = "l", CharEmpty = ".", Format = "|%s| [%d / %d] [%1.2f%%]" }
))
end
if UiCaptureProblem then
GuiTextCentered(modGUI, 0, 0, string.format("A problem occurred while capturing: %s", UiCaptureProblem))
end
else
GuiTextCentered(modGUI, 0, 0, "Done!")
end
GuiLayoutEnd(modGUI)
end end

View File

@ -12,6 +12,7 @@
local libPath = "mods/noita-mapcap/files/libraries/" local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath) dofile(libPath .. "noita-api/compatibility.lua")(libPath)
-- TODO: Replace Noita's coroutine lib with something better
if not async then if not async then
require("coroutines") -- Loads Noita's coroutines library from `data/scripts/lib/coroutines.lua`. require("coroutines") -- Loads Noita's coroutines library from `data/scripts/lib/coroutines.lua`.
end end
@ -23,7 +24,7 @@ end
local CameraAPI = require("noita-api.camera") local CameraAPI = require("noita-api.camera")
local Coords = require("coordinates") local Coords = require("coordinates")
local DebugAPI = require("noita-api.debug") local DebugAPI = require("noita-api.debug")
--local LiveReload = require("noita-api.live-reload") local LiveReload = require("noita-api.live-reload")
local Vec2 = require("noita-api.vec2") local Vec2 = require("noita-api.vec2")
----------------------- -----------------------
@ -31,7 +32,10 @@ local Vec2 = require("noita-api.vec2")
----------------------- -----------------------
Capture = Capture or {} Capture = Capture or {}
Check = Check or {}
Config = Config or {} Config = Config or {}
Message = Message or {}
Modification = Modification or {}
UI = UI or {} UI = UI or {}
------------------------------- -------------------------------
@ -40,6 +44,9 @@ UI = UI or {}
dofile("mods/noita-mapcap/files/capture.lua") dofile("mods/noita-mapcap/files/capture.lua")
dofile("mods/noita-mapcap/files/config.lua") dofile("mods/noita-mapcap/files/config.lua")
dofile("mods/noita-mapcap/files/check.lua")
dofile("mods/noita-mapcap/files/message.lua")
dofile("mods/noita-mapcap/files/modification.lua")
dofile("mods/noita-mapcap/files/ui.lua") dofile("mods/noita-mapcap/files/ui.lua")
-------------------- --------------------
@ -48,9 +55,13 @@ dofile("mods/noita-mapcap/files/ui.lua")
---Called in order upon loading a new(?) game. ---Called in order upon loading a new(?) game.
function OnModPreInit() function OnModPreInit()
-- Set magic numbers based on mod settings.
local config, magic = Modification.RequiredChanges()
Modification.SetMagicNumbers(magic)
-- Override virtual resolution and some other stuff. -- Override virtual resolution and some other stuff.
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/64.xml") --ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/1024.xml")
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/fast-cam.xml") ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/fast-cam.xml")
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/no-ui.xml") --ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/no-ui.xml")
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/offset.xml") --ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/offset.xml")
@ -84,19 +95,29 @@ end
---Called *every* time the game is about to start updating the world. ---Called *every* time the game is about to start updating the world.
function OnWorldPreUpdate() function OnWorldPreUpdate()
-- Coroutines aren't run every frame in this lua sandbox, do it manually here. Message:CatchException("OnWorldPreUpdate", function ()
wake_up_waiting_threads(1)
-- Coroutines aren't run every frame in this lua sandbox, do it manually here.
wake_up_waiting_threads(1)
end)
end end
---Called *every* time the game has finished updating the world. ---Called *every* time the game has finished updating the world.
function OnWorldPostUpdate() function OnWorldPostUpdate()
-- Draw UI after coroutines have been resumed.
UI:Draw()
-- Reload mod every 60 frames. -- Reload mod every 60 frames.
-- This allows live updates to the mod while Noita is running. -- This allows live updates to the mod while Noita is running.
-- !!! DISABLE THIS LINE AND THE CORRESPONDING REQUIRE BEFORE COMMITTING !!! -- !!! DISABLE THIS LINE AND THE CORRESPONDING REQUIRE BEFORE COMMITTING !!!
--LiveReload:Reload("mods/noita-mapcap/", 60) --LiveReload:Reload("mods/noita-mapcap/", 60)
Message:CatchException("OnWorldPostUpdate", function ()
Check:Resolutions(60)
-- Draw UI after coroutines have been resumed.
UI:Draw()
end)
end end
---Called when the biome config is loaded. ---Called when the biome config is loaded.
@ -109,6 +130,8 @@ function OnMagicNumbersAndWorldSeedInitialized()
-- Get resolutions for correct coordinate transformations. -- Get resolutions for correct coordinate transformations.
-- This needs to be done once all magic numbers are set. -- This needs to be done once all magic numbers are set.
Coords:ReadResolutions() Coords:ReadResolutions()
Check:Startup()
end end
---Called when the game is paused or unpaused. ---Called when the game is paused or unpaused.

View File

@ -12,6 +12,7 @@
local libPath = "mods/noita-mapcap/files/libraries/" local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath) dofile(libPath .. "noita-api/compatibility.lua")(libPath)
-- TODO: Replace Noita's mod settings lib with something better. Or at least wrap it: https://stackoverflow.com/questions/9540732/loadfile-without-polluting-global-environment
require("mod_settings") -- Loads Noita's mod settings library from `data/scripts/lib/mod_settings.lua`. require("mod_settings") -- Loads Noita's mod settings library from `data/scripts/lib/mod_settings.lua`.
-------------------------- --------------------------
@ -84,7 +85,8 @@ modSettings = {
id = "capture-mode-spiral-origin-vector", id = "capture-mode-spiral-origin-vector",
ui_name = " Origin", ui_name = " Origin",
ui_description = "", ui_description = "",
value_default = "0, 0", value_default = "0,0",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("capture-mode-spiral-origin").hidden and modSettings:GetNextValue("capture-mode-spiral-origin") == "custom" end, show_fn = function() return not modSettings:Get("capture-mode-spiral-origin").hidden and modSettings:GetNextValue("capture-mode-spiral-origin") == "custom" end,
}, },
@ -101,7 +103,8 @@ modSettings = {
id = "area-top-left", id = "area-top-left",
ui_name = " Top left corner", ui_name = " Top left corner",
ui_description = "The top left corner of the to be captured rectangle.", ui_description = "The top left corner of the to be captured rectangle.",
value_default = "-512, -512", value_default = "-512,-512",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("area").hidden and modSettings:GetNextValue("area") == "custom" end, show_fn = function() return not modSettings:Get("area").hidden and modSettings:GetNextValue("area") == "custom" end,
}, },
@ -109,7 +112,8 @@ modSettings = {
id = "area-bottom-right", id = "area-bottom-right",
ui_name = " Bottom right corner", ui_name = " Bottom right corner",
ui_description = "The bottom right corner of the to be captured rectangle.", ui_description = "The bottom right corner of the to be captured rectangle.",
value_default = "512, 512", value_default = "512,512",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("area").hidden and modSettings:GetNextValue("area") == "custom" end, show_fn = function() return not modSettings:Get("area").hidden and modSettings:GetNextValue("area") == "custom" end,
}, },
@ -134,9 +138,9 @@ modSettings = {
show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end, show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end,
}, },
{ {
id = "pixel-size", id = "pixel-scale",
ui_name = "Pixel size", ui_name = "Pixel scale",
ui_description = "How big a single resulting pixel will be.\nThis is the ratio of image to world pixels.\nA setting of 0 disables any scaling.", ui_description = "How big a single resulting pixel will be.\nThis is the ratio of image to world pixels.\nA setting of 0 disables any scaling.\n \nDon't change while capturing,\nOr you will get unstitchable results.",
value_default = 1, value_default = 1,
value_min = 0, value_min = 0,
value_max = 8, value_max = 8,
@ -165,7 +169,8 @@ modSettings = {
id = "window-resolution", id = "window-resolution",
ui_name = " Window resolution", ui_name = " Window resolution",
ui_description = "Size of the window in screen pixels.", ui_description = "Size of the window in screen pixels.",
value_default = "1024, 1024", value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART, scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
show_fn = function() show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live")) return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
@ -176,7 +181,8 @@ modSettings = {
id = "internal-resolution", id = "internal-resolution",
ui_name = " Internal resolution", ui_name = " Internal resolution",
ui_description = "Size of the viewport in screen pixels.\nIdeally set to the window resolution.", ui_description = "Size of the viewport in screen pixels.\nIdeally set to the window resolution.",
value_default = "1024, 1024", value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART, scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
show_fn = function() show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live")) return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
@ -187,13 +193,59 @@ modSettings = {
id = "virtual-resolution", id = "virtual-resolution",
ui_name = " Virtual resolution", ui_name = " Virtual resolution",
ui_description = "Size of the viewport in world pixels.", ui_description = "Size of the viewport in world pixels.",
value_default = "1024, 1024", value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART, scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
show_fn = function() show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live")) return (not modSettings:Get("advanced.settings.custom-resolution-live").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
or (not modSettings:Get("advanced.settings.custom-resolution-other").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-other")) or (not modSettings:Get("advanced.settings.custom-resolution-other").hidden and modSettings:GetNextValue("advanced.settings.custom-resolution-other"))
end, end,
}, },
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-interval",
ui_name = "Capture interval",
ui_description = "Capturing interval in frames.",
value_default = 60,
value_min = 5,
value_max = 240,
value_display_multiplier = 1,
value_display_formatting = " $0 frames",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-min-distance",
ui_name = "Min. capture distance",
ui_description = "The distance the viewport has to move to allow another screenshot.\nIn world pixels.",
value_default = 10,
value_min = 0,
value_max = 200,
value_display_multiplier = 1,
value_display_formatting = " $0 pixels",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-max-distance",
ui_name = "Max. capture distance",
ui_description = "The distance the viewport has to move to force another screenshot.\nIn world pixels.",
value_default = 50,
value_min = 0,
value_max = 200,
value_display_multiplier = 1,
value_display_formatting = " $0 pixels",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
},
{ {
id = "capture-entities", id = "capture-entities",
ui_name = "Capture entities", ui_name = "Capture entities",
@ -227,22 +279,6 @@ modSettings = {
}, },
} }
}, },
{
category_id = "actions",
ui_name = "ACTIONS",
foldable = true,
_folded = true,
settings = {
{
id = "button-open-output",
ui_name = "Open output directory",
ui_description = "Reveals the output directory in your file browser.",
scope = MOD_SETTING_SCOPE_RUNTIME,
ui_fn = customSettingButton,
change_fn = function() print("test") end,
},
}
},
} }
---Hide/unhide some settings based on other settings. ---Hide/unhide some settings based on other settings.