Compare commits

..

8 Commits

Author SHA1 Message Date
f7ac3c4009 Add support for more Noita builds
All checks were successful
Build and test / Build and test (push) Successful in 2m47s
- Build Aug 12 2024 21:10:13
- Build Aug 12 2024 21:48:01
2024-10-03 21:17:07 +02:00
David Vogel
09d8bcfe09
Merge pull request #31 from WUOTE/12-August-2024-latest
Add support for the latest Beta branch build
2024-09-02 13:59:12 +02:00
b76233caa4 Fix indentation of modifications.lua 2024-09-02 13:57:29 +02:00
b6224e657f Add support for newest dev build 2024-09-02 13:56:22 +02:00
WUOTE
3e5950810a Update modification.lua 2024-09-02 16:44:49 +06:00
d3edf29a80 Add animation capture mode 2024-07-03 00:16:38 +02:00
David Vogel
3fd0d970b7
Merge pull request #30 from Dadido3/dependabot/go_modules/golang.org/x/image-0.18.0
Bump golang.org/x/image from 0.14.0 to 0.18.0
2024-06-26 21:55:23 +02:00
dependabot[bot]
627dc545d4
Bump golang.org/x/image from 0.14.0 to 0.18.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.14.0 to 0.18.0.
- [Commits](https://github.com/golang/image/compare/v0.14.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 19:38:05 +00:00
8 changed files with 141 additions and 18 deletions

4
.gitignore vendored
View File

@ -108,4 +108,6 @@ $RECYCLE.BIN/
/bin/stitch/*.png /bin/stitch/*.png
/bin/stitch/*.dzi /bin/stitch/*.dzi
/bin/stitch/*_files/ /bin/stitch/*_files/
/files/magic-numbers/generated.xml /files/magic-numbers/generated.xml
/bin/stitch/captures/*

View File

@ -81,6 +81,10 @@ After a few minutes the file `output.png` will be created.
- `Spiral`: Will capture the world in a spiral. - `Spiral`: Will capture the world in a spiral.
The center starting point of the spiral can either be your current viewport, the world center or some custom coordinates. The center starting point of the spiral can either be your current viewport, the world center or some custom coordinates.
- `Animation`: Will capture an image sequence.
This will capture whatever you see frame by frame and stores it in the output folder by frame number.
You can't stitch the resulting images, but instead you can use something like ffmpeg to render the sequence into a video file.
### Advanced mod settings ### Advanced mod settings
- `World seed`: If non empty, this will set the next new game to this seed. - `World seed`: If non empty, this will set the next new game to this seed.

View File

@ -166,6 +166,37 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
MonitorStandby.ResetTimer() MonitorStandby.ResetTimer()
end end
---Captures a screenshot of the current viewport.
---This is used to capture animations, therefore the resulting image may not be suitable for stitching.
---@param outputPixelScale number? The resulting image pixel to world pixel ratio.
---@param frameNumber integer The frame number of the animation.
local function captureScreenshotAnimation(outputPixelScale, frameNumber)
if outputPixelScale == 0 or outputPixelScale == nil then
outputPixelScale = Coords:PixelScale()
end
local rectTopLeft, rectBottomRight = ScreenCapture.GetRect()
if not rectTopLeft or not rectBottomRight then
error(string.format("couldn't determine capturing rectangle"))
end
if Coords:InternalRectSize() ~= rectBottomRight - rectTopLeft then
error(string.format("internal rectangle size seems to have changed from %s to %s", Coords:InternalRectSize(), rectBottomRight - rectTopLeft))
end
local topLeftWorld, bottomRightWorld = Coords:ToWorld(rectTopLeft), Coords:ToWorld(rectBottomRight)
---We will use this to get our fame number into the filename.
---@type Vec2
local outputTopLeft = Vec2(frameNumber, 0)
if not ScreenCapture.Capture(rectTopLeft, rectBottomRight, outputTopLeft, (bottomRightWorld - topLeftWorld) * outputPixelScale) then
error(string.format("failed to capture screenshot"))
end
-- Reset monitor and PC standby every screenshot.
MonitorStandby.ResetTimer()
end
---Map capture process runner context error handler callback. Just rolls off the tongue. ---Map capture process runner context error handler callback. Just rolls off the tongue.
---@param err string ---@param err string
---@param scope "init"|"do"|"end" ---@param scope "init"|"do"|"end"
@ -750,6 +781,56 @@ function Capture:StartCapturingPlayerPath(interval, outputPixelScale)
self.PlayerPathCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr) self.PlayerPathCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr)
end end
---Starts to capture an animation.
---This stores sequences of images that can't be stitched, but can be rendered into a video instead.
---Use `Capture.MapCapturingCtx` to stop, control or view the process.
---@param outputPixelScale number? -- The resulting image pixel to world pixel ratio.
function Capture:StartCapturingAnimation(outputPixelScale)
---Queries the mod settings for the live capture parameters.
---@return integer interval -- The interval length in frames.
local function querySettings()
local interval = 1--tonumber(ModSettingGet("noita-mapcap.live-interval")) or 30
return interval
end
-- 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.
---@param ctx ProcessRunnerCtx
local function handleDo(ctx)
Modification.SetCameraFree(false)
local frame = 0
repeat
local interval = querySettings()
-- Wait until we are allowed to take a new screenshot.
local delayFrames = 0
repeat
wait(0)
delayFrames = delayFrames + 1
until ctx:IsStopping() or delayFrames >= interval
captureScreenshotAnimation(outputPixelScale, frame)
frame = frame + 1
until ctx:IsStopping()
end
---Process end callback.
---@param ctx ProcessRunnerCtx
local function handleEnd(ctx)
Modification.SetCameraFree()
end
-- Run process, if there is no other running right now.
self.MapCapturingCtx:Run(nil, handleDo, handleEnd, mapCapturingCtxErrHandler)
end
---Starts the capturing process based on user/mod settings. ---Starts the capturing process based on user/mod settings.
function Capture:StartCapturing() function Capture:StartCapturing()
Message:CatchException("Capture:StartCapturing", function() Message:CatchException("Capture:StartCapturing", function()
@ -762,6 +843,8 @@ function Capture:StartCapturing()
if mode == "live" then if mode == "live" then
self:StartCapturingLive(outputPixelScale) self:StartCapturingLive(outputPixelScale)
self:StartCapturingPlayerPath(5, outputPixelScale) -- Capture player path with an interval of 5 frames. self:StartCapturingPlayerPath(5, outputPixelScale) -- Capture player path with an interval of 5 frames.
elseif mode == "animation" then
self:StartCapturingAnimation(outputPixelScale)
elseif mode == "area" then elseif mode == "area" then
local area = ModSettingGet("noita-mapcap.area") local area = ModSettingGet("noita-mapcap.area")
if area == "custom" then if area == "custom" then

View File

@ -120,7 +120,7 @@ function Check:Regular(interval)
-- This is not perfect, as it doesn't take rounding and cropping into account, so the actual captured area may be a few pixels smaller. -- This is not perfect, as it doesn't take rounding and cropping into account, so the actual captured area may be a few pixels smaller.
local mode = ModSettingGet("noita-mapcap.capture-mode") local mode = ModSettingGet("noita-mapcap.capture-mode")
local captureGridSize = tonumber(ModSettingGet("noita-mapcap.grid-size")) local captureGridSize = tonumber(ModSettingGet("noita-mapcap.grid-size"))
if mode ~= "live" and (Coords.VirtualResolution.x < captureGridSize or Coords.VirtualResolution.y < captureGridSize) then if (mode ~= "live" and mode ~= "animation") and (Coords.VirtualResolution.x < captureGridSize or Coords.VirtualResolution.y < captureGridSize) then
Message:ShowGeneralSettingsProblem( Message:ShowGeneralSettingsProblem(
"The virtual resolution is smaller than the capture grid size.", "The virtual resolution is smaller than the capture grid size.",
"This means that you will get black areas in your final stitched image.", "This means that you will get black areas in your final stitched image.",

View File

@ -248,6 +248,26 @@ function Modification.SetMemoryOptions(memory)
mPlayerNeverDies = function(value) ffi.cast("char*", 0x0131D8DC+6)[0] = value end, mPlayerNeverDies = function(value) ffi.cast("char*", 0x0131D8DC+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x0131D8DC+7)[0] = value end, mFreezeAI = function(value) ffi.cast("char*", 0x0131D8DC+7)[0] = value end,
}, },
{_Offset = 0x0118FD3C, _BuildString = "Build Aug 12 2024 21:10:13", -- Steam dev build.
mPostFxDisabled = function(value) ffi.cast("char*", 0x01327D3C+0)[0] = value end,
mGuiDisabled = function(value) ffi.cast("char*", 0x01327D3C+1)[0] = value end,
mGuiHalfSize = function(value) ffi.cast("char*", 0x01327D3C+2)[0] = value end,
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x01327D3C+3)[0] = value end,
mTrailerMode = function(value) ffi.cast("char*", 0x01327D3C+4)[0] = value end,
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x01327D3C+5)[0] = value end,
mPlayerNeverDies = function(value) ffi.cast("char*", 0x01327D3C+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x01327D3C+7)[0] = value end,
},
{_Offset = 0x0118FD3C, _BuildString = "Build Aug 12 2024 21:43:22", -- Steam dev build.
mPostFxDisabled = function(value) ffi.cast("char*", 0x01327D3C+0)[0] = value end,
mGuiDisabled = function(value) ffi.cast("char*", 0x01327D3C+1)[0] = value end,
mGuiHalfSize = function(value) ffi.cast("char*", 0x01327D3C+2)[0] = value end,
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x01327D3C+3)[0] = value end,
mTrailerMode = function(value) ffi.cast("char*", 0x01327D3C+4)[0] = value end,
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x01327D3C+5)[0] = value end,
mPlayerNeverDies = function(value) ffi.cast("char*", 0x01327D3C+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x01327D3C+7)[0] = value end,
},
}, },
}, },
[false] = { [false] = {
@ -365,12 +385,26 @@ function Modification.SetMemoryOptions(memory)
end, end,
}, },
{_Offset = 0x01001DF4, _BuildString = "Build Apr 8 2024 18:11:27", -- Steam build. {_Offset = 0x01001DF4, _BuildString = "Build Apr 8 2024 18:11:27", -- Steam build.
enableModDetection = function(value) enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3355+6) local ptr = ffi.cast("char*", 0x006B3355+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE) Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled. ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end, end,
}, },
{_Offset = 0x01007CA4, _BuildString = "Build Aug 12 2024 21:14:23", -- Steam build.
enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3925+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end,
},
{_Offset = 0x01007CA4, _BuildString = "Build Aug 12 2024 21:48:01", -- Steam build.
enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3925+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end,
},
}, },
}, },
} }

4
go.mod
View File

@ -35,9 +35,9 @@ require (
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/tdewolff/minify/v2 v2.20.10 // indirect github.com/tdewolff/minify/v2 v2.20.10 // indirect
github.com/tdewolff/parse/v2 v2.7.7 // indirect github.com/tdewolff/parse/v2 v2.7.7 // indirect
golang.org/x/image v0.14.0 // indirect golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.16.0 // indirect
star-tex.org/x/tex v0.4.0 // indirect star-tex.org/x/tex v0.4.0 // indirect
) )

8
go.sum
View File

@ -87,8 +87,8 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
@ -105,8 +105,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=

View File

@ -67,9 +67,9 @@ modSettings = {
{ {
id = "capture-mode", id = "capture-mode",
ui_name = "Mode", ui_name = "Mode",
ui_description = "How the mod captures:\n- Live: Capture as you play along.\n- Area: Capture a defined area of the world.\n- Spiral: Capture in a spiral around a starting point indefinitely.", ui_description = "How the mod captures:\n- Live: Capture as you play along.\n- Area: Capture a defined area of the world.\n- Spiral: Capture in a spiral around a starting point indefinitely.\n- Animation: Capture the screen frame by frame.",
value_default = "live", value_default = "live",
values = { { "live", "Live" }, { "area", "Area" }, { "spiral", "Spiral" } }, values = { { "live", "Live" }, { "area", "Area" }, { "spiral", "Spiral" }, { "animation", "Animation"} },
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
}, },
{ {
@ -143,7 +143,7 @@ modSettings = {
value_default = "512", value_default = "512",
allowed_characters = "0123456789", allowed_characters = "0123456789",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end, show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end,
}, },
{ {
id = "pixel-scale", id = "pixel-scale",
@ -167,7 +167,7 @@ modSettings = {
value_display_multiplier = 1, value_display_multiplier = 1,
value_display_formatting = " $0 frames", value_display_formatting = " $0 frames",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end, show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end,
}, },
{ {
id = "custom-resolution-live", id = "custom-resolution-live",